✍ Evan TraversEvan Travers' Personal Bloghttps://evantravers.com/2024-03-23T13:50:00+00:00Evan TraversAlternatives to the Corne-ish Zenhttps://evantravers.com/articles/2024/03/22/alternatives-to-the-corne-ish-zen/2024-03-22T13:32:00+00:002024-03-22T11:04:32+00:00Evan Travers<p>I've gotten a lot of emails from those of you who saw <a href="https://evantravers.com/articles/2022/04/19/review-corne-ish-zen/">my post on the Corne-ish Zen</a> and want one for themselves. Sadly the group buys for the Zen are closed. There is no plan to make more at this point.</p>
<p>As of March 2024 here are a couple alternatives...</p><p>I've gotten a lot of emails from those of you who saw <a href="https://evantravers.com/articles/2022/04/19/review-corne-ish-zen/">my post on the Corne-ish Zen</a> and want one for themselves. Sadly the group buys for the Zen are closed. There is no plan to make more at this point.</p>
<p>As of March 2024 here are a couple alternatives to look into if the idea of a small premium split keyboard is appealing.</p>
<h2 id="typeractive-5-column-corne">Typeractive 5 Column Corne</h2>
<p><img src="https://pbs.twimg.com/media/FpYVt5bXEAIrv6r?format=jpg&name=large" alt="corne from twitter">
(Image from <a href="https://twitter.com/joe_scotto/status/1627512199307558912/photo/2">@joe_scotto on X</a>)</p>
<p>The <a href="https://typeractive.xyz/pages/build/corne-5col_choc">Typeractive 5-Column Corne</a> kit appears to be <em>very</em> similar to the Corne-ish Zen, especially when it comes to the choc-spacing of the keys. It's also pretty affordable. I may pick one up as a backup.</p>
<p>Joe Scotto has a great video on how to build one:</p>
<div class="youtube-container">
<iframe
width="100%"
height="100%"
src="https://www.youtube.com/embed/FJgvi7WShxY"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>
</div>
<h2 id="ferris-sweep-crab-broom">Ferris Sweep (Crab Broom)</h2>
<p><img src="https://www.boardsource.xyz/_next/image?url=https%3A%2F%2Fimages.boardsource.xyz%2Fferris_sweep_aluminum_main_16_9-1709737446329.jpg&w=1920&q=75" alt="crab broom"></p>
<p>The <a href="https://www.boardsource.xyz/products/crab-broom">Crab Broom aluminum case</a> for the Ferris Sweep is a very nice looking setup with even <em>fewer</em> keys. I'm <a href="https://github.com/evantravers/zmk-config/compare/6a29008b60d1dd1012f790c321f3b4ff81bf58a0%E2%80%A6c7eefd3b609eb39f603d236e8888f8126784c9e1">playing with ZMK combos</a> to see if I can make myself go down to 3x5_2.</p>
<p>I have said that the only thing that would make me give up my Zen would be another Zen with a more pronounced curve… like the ergonomic curve on the Ferris. It really resembles my very comfy Atreus. I may end up buying on of these if I can budget the funds.</p>
<h2 id="part-b8a856d">…</h2>
<p>There are a lot of wonderful makers in this space, each with their own take and specialty. I <em>know</em> I'm missing a bunch of options but these are two to which I've found myself emailing folks over the past few weeks.</p>
<p>Some other places to explore:</p>
<ul>
<li>My friend Jesse is launching <a href="https://twitter.com/1337keyboards">1337 keyboards</a> very soon. Cornes and ninjuni with <a href="https://www.reddit.com/r/ErgoMechKeyboards/comments/1b21eih/nijuni/">lovely woodcut cases</a>.</li>
<li>The <a href="https://www.moergo.com/">Glove80</a> appears to be a strong contender for those who want a pre-built with a few more keys.</li>
<li>beekeeb makes an <a href="https://shop.beekeeb.com/product/presoldered-chocofi-split-keyboard/">interesting chocofi option</a>, just no aluminum cases that I can see.</li>
<li>keebio is about to release the <a href="https://keeb.io/products/chiri-ce-low-profile-choc-hotswap-split-ergonomic-keyboard?variant=40306328993886">chiri</a> which has an aluminum case and 3x5 spacing. Doesn't look wireless though.</li>
</ul>
Recently: Obsidianhttps://evantravers.com/articles/2024/03/14/recently-obsidian/2024-03-14T20:39:00+00:002024-03-23T09:02:53+00:00Evan Travers<p><img alt='' src='https://evantravers.com/images/articles/2024/03/graph.png'></p><p>I'm still using Obsidian. For a while it was the only icon on my iOS dock.</p>
<h2 id="bible-study">Bible Study</h2>
<p><img src="/images/articles/2024/03/bible.png" alt="bible study"></p>
<p>I use a downloaded and modified <a href="https://forum.obsidian.md/t/bible-study-in-obsidian-kit-including-the-bible-in-markdown/12503">markdown bible</a> as my main study bible. I have it split by chapter. That makes it very readable.</p>
<p>Paying for Obsidian Sync has...</p><p><img alt='' src='https://evantravers.com/images/articles/2024/03/graph.png'></p><p>I'm still using Obsidian. For a while it was the only icon on my iOS dock.</p>
<h2 id="bible-study">Bible Study</h2>
<p><img src="/images/articles/2024/03/bible.png" alt="bible study"></p>
<p>I use a downloaded and modified <a href="https://forum.obsidian.md/t/bible-study-in-obsidian-kit-including-the-bible-in-markdown/12503">markdown bible</a> as my main study bible. I have it split by chapter. That makes it very readable.</p>
<p>Paying for Obsidian Sync has really helped startup time on iOS… iCloud sync would "unload" all but the most recent hundred or so files.</p>
<p>I use <a href="https://github.com/TfTHacker/obsidian42-strange-new-worlds">Obsidian Strange New Worlds</a> to put counters on headers that have links, so I can see what verses have notes linked to them. Because I had run a script over my old files, much of my old sermon notes and journal entries are linked into my bible. I often come across things I wrote almost twenty years ago, and that's pretty cool.</p>
<h2 id="journaling">Journaling</h2>
<p><img src="/images/articles/2024/03/weekly.png" alt="weekly journal summary"></p>
<p>I'm still doing the <a href="https://evantravers.com/articles/2022/09/12/journaling-and-reviews-in-obsidian/">wild ritual-based summarizing journaling system</a>. It's still a powerful force for change, especially the <a href="https://evantravers.com/articles/2024/03/28/obsidian-12wy-review-template/">12WY style reviews</a>.</p>
<p>The funnel of summaries is constructed to help me avoid staying in a bad pattern of behavior and easily see progress<sup id="fnref1"><a href="#fn1">1</a></sup>. </p>
<h2 id="note-making">Note Making</h2>
<figure>
<img src='/images/articles/2024/03/evergreen.png'>
<figcaption>
An example of an Evergreen note, spawned from a booknote, featuring Excalidraw.
</figcaption>
</figure>
<p>I'm doing a lot of thinking and writing in Obsidian. I've developed a personal taxonomy for my own system of atomic notes with <a href="https://evantravers.com/articles/2021/10/30/lifecycle-of-notes-my-implementation/">a lifecycle that hasn't changed much since my last post</a>:</p>
<ul>
<li>unnamed notes:
<ul>
<li>low confidence</li>
<li>contains at least one link</li>
<li>title based on ID</li>
</ul></li>
<li>Evergreen Notes:
<ul>
<li>contains strong fact or strong opinion</li>
<li>title based on a phrase that represents an "API" to that idea for my brain</li>
</ul></li>
</ul>
<p>I also have a couple special forms:</p>
<ul>
<li>Journal notes in a <code>/journal</code> folder.</li>
<li>book notes in a <code>/booknotes</code> folder… sometimes alongside the <a href="https://github.com/hadynz/obsidian-kindle-plugin">imported Kindle highlights</a>. (Usually a tweet-length summary, some takeaways, any really impactful ideas usually become Evergreen Notes.)</li>
<li>a work-related wiki that generally follows my <a href="https://evantravers.com/articles/2021/11/08/border-of-the-known/">border of the known theory</a>.</li>
<li>MOC-style notes that usually have a tag-style<sup id="fnref2"><a href="#fn2">2</a></sup> filename (i.e. <code>Journaling.md</code>) and a random list or query to link to other notes.</li>
<li>article/essay drafts in a <code>/writing</code> folder</li>
<li><a href="https://obsidian.md/canvas">Obsidian Canvas</a> usually in the form of a question that I'm exploring. (i.e. "What do I think about generative AI?" or "What is a biblical response to burnout?")</li>
</ul>
<figure>
<img src='/images/articles/2024/03/canvas.png'>
<figcaption>
<p>
I am <b>really</b> loving Canvas. I need to write a separate post about it… but as a teaser: persistent spatial thinking is easier on canvas because you don't need to save a workspace or <a href='https://help.obsidian.md/User+interface/Tabs#Stack+tab+groups'>Andy-mode stacked tabs</a>. The act of drawing a line between two thoughts, and writing a label to describe why the two are related is a powerful creative act. More later.
</p>
</figcaption>
</figure>
<h2 id="writing">Writing</h2>
<p><img src="/images/articles/2024/03/zen.png" alt="zen mode"></p>
<p>I'm still using the same <a href="https://evantravers.com/articles/2022/08/30/writing-in-obsidian/">kanban system</a> for blog posts like this.</p>
<p>While I used to incubate a post or thought in <a href="https://evantravers.com/articles/tags/draftsapp/">Drafts.app</a> for at least 80% of the lifecycle, I'm pushing it faster into because I want to be able to cultivate it in the context of all the other notes.</p>
<h2 id="plugins">Plugins</h2>
<p>I use <a href="https://github.com/tgrosinger/leader-hotkeys-obsidian">Leader Hotkeys</a> to bind the exact same window management settings that I use in tmux.</p>
<p>As before mentioned I use <a href="https://github.com/TfTHacker/obsidian42-strange-new-worlds">Obsidian Strange New Worlds</a> to explore links between blocks, especially Bible verses.</p>
<p>My <a href="https://github.com/tgrosinger/leader-hotkeys-obsidian">crazy journaling system</a> uses <a href="https://github.com/liamcain/obsidian-periodic-notes">Periodic Notes</a>, <a href="https://github.com/phibr0/obsidian-charts">Charts</a>, and <a href="https://blacksmithgu.github.io/obsidian-dataview/">Dataview</a> to generate the graphs and summary charts.</p>
<p>Evergreen Notes and Booknotes often use <a href="https://github.com/zsviczian/obsidian-excalidraw-plugin">Excalidraw</a> to store and reproduce simple diagrams and illustrations. It's essentially a glorified SVG under the hood, so I have a lot of confidence that it won't disappear quickly. It doesn't take up much space, and it is easily trackable by source control. If it was mission critical I'd likely use image files.</p>
<p>I built a minimal writing mode for Obsidian using <a href="https://github.com/deathau/cm-typewriter-scroll-obsidian">Typewriter Scroll</a>, <a href="https://github.com/kepano/obsidian-hider">Hider</a>, and <a href="https://github.com/phibr0/obsidian-commander">Commander</a> just like the one I use in nvim. Essentially I use Commander to create a single command that triggers several commands. I type <code><leader>-m</code> which calls a Macro from Commander that runs five commands:</p>
<ul>
<li>Hider: Toggle tab bar</li>
<li>Hider: Toggle app ribbon</li>
<li>Hider: Toggle status bar</li>
<li>Typewriter Scroll: Toggle Zen Mode On/Off</li>
<li>Typewriter Scroll: Toggle On/Off</li>
</ul>
<p>I just started using <a href="https://github.com/PKM-er/media-extended">Media Extended</a> to take notes on videos and lectures. It's early, but I'm excited. I can open a Youtube video in Obsidian and insert timestamped links in another note as I watch.</p>
<p>I use <a href="https://quickadd.obsidian.guide/docs/">QuickAdd</a> and <a href="https://github.com/SilentVoid13/Templater">Templater</a> to construct Booknotes with Google Books API. This is way buggier than <a href="https://evantravers.com/articles/2020/03/19/simple-markdown-zettelkasten-drafts-app/#book-notes">my previous method using Drafts</a>, I think mostly the issue is the .js file or plugin settings getting munged by Obsidian Sync. Unsure.</p>
<p>I use <a href="https://github.com/denolehov/obsidian-git">Git</a> to periodically backup to Github just in case, or before I make some drastic changes.</p>
<p>I use <a href="https://github.com/tgrosinger/advanced-tables-obsidian">Advanced Tables</a> on a few of my notes for research or adding up sums. Most notably I moved my wishlist out of Amazon and Pinterest and am now handling it locally.</p>
<p>If I need to any honest-to-goodness text editing, I'll drop into nvim to write on the plaintext file and use Obsidian as a glorified preview mode. Playing with using <a href="https://valentjn.github.io/ltex/">ltex</a> and <a href="https://github.com/artempyanykh/marksman/">Marksman</a> as LSP to drive wiki-style neovim usage instead of a core plugin system.</p>
<h2 id="wishes">Wishes</h2>
<p>I want to re-download and transform my markdown bible again, this time without hyphens (<code>-</code>) in the file name. Ideally a book + chapter would naturally match as an <a href="https://help.obsidian.md/Plugins/Outgoing+links">Unlinked Mention</a>. (i.e. "2 Corinthians 5")</p>
<p>I want to smush my publishing/articles system and my thinking system together into one repo. Not sure how to do that yet without losing <a href="https://evantravers.com/articles/2019/11/08/using-git-to-generate-a-changelog-for-your-blog/">the git history</a>.</p>
<p>I really want to move my contact cards for <a href="https://evantravers.com/articles/2024/02/02/digital-prayer-journal-using-contacts/">prayer journaling into Obsidian</a>. I wish there was a way to access and refer to them in plaintext notes and still sync to my phone.</p>
<p>I would like to start writing a book on journaling, maybe I should start a PARA-style folder inside <code>/writing</code>.</p>
<h2 id="part-b8a856d">…</h2>
<p>That's it for the moment. Obsidian has become my most used app for thinking, absorbing my beloved Mindnode and Drafts into one big workbench.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>We just went through annual reviews, and the quarterly summaries made it easy for me to find my story and searching my journal allowed me to find specific praise for those around me. <a href="#fnref1">↩</a></p>
</li>
<li id="fn2">
<p>Many of the tags that I have previously used (i.e. "prayer," "keyboards," or "selfhelp") turn into MOC notes now. As I start collecting/refactoring the notes under that tag I end up creating a note by the same name and starting to write about what interests me under that tag. I follow Nick Milo's pattern of linking to notes and as I start explain why they are linked to the central idea, I often start discovering new subwebs of thought or conversations that become essays like this one. <a href="#fnref2">↩</a></p>
<p>I still have a lot of tags to unwind into MOC this way, but I'm just doing it organically as my interests guide me.</p>
</li>
</ol>
</div>
On Augmented Realityhttps://evantravers.com/articles/2024/03/07/on-augmented-reality/2024-03-07T23:23:00+00:002024-03-07T17:26:00+00:00Evan Travers<p>Half a decade ago I tried on a first generation AR headset, an early <a href="https://en.wikipedia.org/wiki/Microsoft_HoloLens">Hololens</a> prototype. A low-resolution screen (the size of a deck of cards at arms length) floated in my view, revealing a holographic supercar. Leaning in and out towards the engine...</p><p>Half a decade ago I tried on a first generation AR headset, an early <a href="https://en.wikipedia.org/wiki/Microsoft_HoloLens">Hololens</a> prototype. A low-resolution screen (the size of a deck of cards at arms length) floated in my view, revealing a holographic supercar. Leaning in and out towards the engine block you could block out everything but the tiny screen and believe for a second there was an object there that no one else could see… but the technology was too limited to really sell the illusion for more than a moment.</p>
<p>This year there are several competitive AR/VR headsets in the gaming/entertainment space, and Apple released the much-vaunted Vision Pro. While it is still early, we can be sure that there are lower-priced and better-featured Vision devices maturing within the unmarked rooms in Cupertino. We've been promised virtual reality for decades now. It remains to be seen whether this the moment.</p>
<p>I've been thinking about what this technology means as a designer, user, and as a father. Here are some random thoughts.</p>
<h2 id="just-add-magic">Just Add Magic</h2>
<p>I'm excited for the design challenges that come with dimensionality. Since before <a href="https://evantravers.com/articles/2021/04/14/scifi-drives-the-future/#1968-the-mother-of-all-demos">the mother of all demos</a> designers have been emulating three dimensional reality with visual tricks: <a href="https://appleinsider.com/articles/22/08/23/what-apple-learned-from-skeuomorphism-and-why-it-still-matters">shadows</a>, hues, <a href="https://m3.material.io/foundations/interaction/states/state-layers">layers.</a>. We mock and <a href="https://www.figma.com/blog/how-we-built-spring-animations/">mimic physics</a> in order to help the user understand the purpose and affordances of the untouchable object hiding behind the sheet of glass.</p>
<p>Designers struggle to make things simple. They take an experience or object and attempt to add magic.</p>
<p>As an example, a designer making a computer solitaire game spends immense time crafting a 2d emulation of interacting with cards. He aims to convince the user that giving up the physical interactivity and engagement is worth computer system handling shuffling and the rules. Screens and mice instead of tables and laughs… but with the convenience of digital magic.</p>
<p>A virtual reality system simply hands you the cards. No persuasion needed. The designer then focuses on adding magic to the recognizable physical card system. We see something recognizable, but magical. Virtual objects can be familiar to the user yet surprise them by transcending normal physics and limitations:</p>
<ul>
<li>A rolodex that sorts by recency and transforms the avatar of person as you start the call.</li>
<li>Recipe books that filter to your guest's dietary restrictions.</li>
<li>Roads that emphasize your destination and preferred fast food joints.</li>
<li>Sticky notes on a wall accessible by teams across continents and timezones</li>
<li>Tools that are simple and easy to understand and use and yet take no weight to carry.<sup id="fnref1"><a href="#fn1">1</a></sup></li>
</ul>
<p>Imagine: instead of an amorphous screenslab your working tools appear to yourself and to others <em>as what they are</em>: a camera, a writing tablet, an actual stack of todo stickies, a deck of contact cards.</p>
<p>Spatial Computing/VR/AR pushes digital product design up out of two dimensions and into the existing design domains of Service Design or Environmental Design… <a href="https://evantravers.com/articles/2022/10/03/working-backwards-from-magic/">but with magic</a>.</p>
<p>It's exciting.</p>
<h2 id="where-there-is-illusion-people-change">Where There is Illusion, People Change.</h2>
<p>That same day I tried a (then) brand new Vive headset. After playing some games for a few minutes I took off the headset to hand to another person. The jarring "teleportation" sensation of being ripped from one world into the real was profound. It has to be experienced, not explained. It seems like <a href="https://www.youtube.com/watch?v=rilqFauUO9g#t=3m32s">newer headsets provide even more immersive</a> experiences.</p>
<p>Location is a powerful force on our behavioral systems. Having a device that can teleport us to another location is powerful tool for perhaps creating creative-only spaces… I easily see myself creating a "location" in which I only write. (I presently use sound and music to try and trick my brain, but I'd pay money for a virtual writing cabin.)</p>
<p>We aren't the first to realize this power.</p>
<p><em>The Diamond Age</em> by Neal Stephenson imagines a magic book given to young Nell. It can teach and guide her to become her full princess potential… and nudges her towards a particular <em>interesting</em> path of life. <em>Ender's Game</em> by Orson Scott Card imagines a similar device: a VR game so real that it teaches character and empathy. Both stories are hopeful. The tools are written by well-meaning people who want the best for the kids exposed to the technology.</p>
<p>I gotta wonder though… who gets to write the magic tools to teach my children? Who decides what is "interesting" in the Primer? Who decides what is good character and healthy empathy?</p>
<p>The same tools that we can use to create positive self-directed behavioral change can be wielded against us.</p>
<p>I have a hard time believing that the same corporations that make their money off of measuring what we click, what causes our scrolling to linger, what words we respond to with action, and measure their employees engagement, lines of code typed, will ignore this new pool of data. The whole apparatus of surveillance capitalism will benefit more than the headset user. We are placing a device on our heads that records our subconscious eye saccades and facial ticks… not metrics associated with our attention, but our <em>actual</em> attention.</p>
<p>We will likely be <a href="https://en.wikipedia.org/wiki/Ivan_Pavlov">Pavlov'd</a> in even more subtle and <a href="https://www.uxdesigninstitute.com/blog/what-are-dark-patterns-in-ux/">darker UX</a> ways than ever before. Having a strong code of ethics as a designer is going to be <em>very</em> important… we will have more power than ever before.</p>
<h2 id="outsourcing-personality">Outsourcing Personality</h2>
<p>In Charlie Stross's novel <em>Accelerando</em><sup id="fnref2"><a href="#fn2">2</a></sup>, a novel telling the story of the singularity, the tech-forward Manfred has his goggles ripped by a street thief named Jack. Manfred suddenly has no clue what he was about to do:</p>
<blockquote>
<p>"I'm Manfred – Manfred. My memory. What's happened to my memory?" Elderly Malaysian tourists point at him from the open top deck of a passing bus. He burns with a sense of horrified urgency. <em>I</em> <em>was</em> <em>going</em> <em>somewhere</em>, he recalls. <em>What</em> <em>was</em> <em>I</em> <em>doing?</em> It was amazingly important, he thinks, but he can't remember what exactly it was. He was going to see someone about – it's on the tip of his tongue –</p>
</blockquote>
<p>Manfred has outsourced so much of his thinking and memory to the computer on his face that his youthful assailant Jack is actually able to conduct his meeting for Manfred… transferring Manfred's goggles with their tasks and agents is basically transferring Manfred's goals and context to a new body.</p>
<blockquote>
<p>In a very real sense, the glasses <em>are</em> Manfred, regardless of the identity of the soft machine with its eyeballs behind the lenses. And it is a very puzzled Manfred who picks himself up and, with a curious vacancy in his head – except for a hesitant request for information about accessories for Russian army boots – dusts himself off and heads for his meeting on the other side of town.</p>
</blockquote>
<p>As someone who regularly writes my thoughts out in a digital format, who uses sophisticated calendars and task management to <a href="https://evantravers.com/articles/2021/06/21/review-do-more-better/">be a good steward</a>… this is a little terrifying. The futuristic ability to carry and see through our technology rather than just consult<sup id="fnref3"><a href="#fn3">3</a></sup> it is powerful and scary.</p>
<p>I'll be clutching <a href="https://evantravers.com/articles/tags/bullet-journal/">my bullet journal</a> a little more tightly.</p>
<h2 id="part-b8a856d">…</h2>
<p>I believe that virtual environments provide an opportunity for product designers and UX designers to stop designing screens and to focus on service design experiences. What is this person trying to do? Where are they standing? Who are they with? How could we enrich a physical tool with raw magic to make a person more focused, creative, and joyful in their work? It is a very exciting time to be interested in building tools to serve other people.</p>
<p>I believe that wearable technology with high fidelity of data on the human experience will enable profit-minded corporations to read and interpret emotion and subconscious action, combined with emotionally resonant and realistic experiences and memories<sup id="fnref4"><a href="#fn4">4</a></sup>. Unprecedented power to enrich and enslave. The ability to actually change what a person sees in the world.</p>
<p>It's a weird time to be in technology. Buckle up.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>The magical item of <a href="http://dnd5e.wikidot.com/wondrous-items:bag-of-holding">Bag of Holding</a> in real world use. <a href="#fnref1">↩</a></p>
</li>
<li id="fn2">
<p><em>Accelerando</em> is a brilliant book that requires some strong content warnings… so you are warned. <a href="#fnref2">↩</a></p>
</li>
<li id="fn3">
<p>Consulting technology as an advisor feels like a relatively healthy mental model. I'm afraid I do much more than consult at this point, but maybe that's a north start to drive towards. <a href="#fnref3">↩</a></p>
</li>
<li id="fn4">
<p>There's a reason that in D&D illusion magic is both under-appreciated and arguably the most powerful of all. Evil creatures in countless stories can shape-shift and change your perception of reality… Steven King's IT, JRR Tolkien's Anatar, Vampires, Demons, superheroes, The Matrix… <a href="#fnref4">↩</a></p>
</li>
</ol>
</div>
Accessing devdocs.io from Hyper Hotkeyhttps://evantravers.com/articles/2024/02/29/accessing-devdocs-io-from-hyper-hotkey/2024-02-29T16:23:00+00:002024-02-29T10:35:15+00:00Evan Travers<p>If you've ever sat in a crowded Starbucks, waiting for the burdened wifi to finally deliver the MDN flex container docs, raise your hand. 🤚</p>
<p>For years I have used <a href="https://kapeli.com/dash">Dash.app by Kapeli</a> to access documentation quickly offline. It is a <em>fantastic</em> app and...</p><p>If you've ever sat in a crowded Starbucks, waiting for the burdened wifi to finally deliver the MDN flex container docs, raise your hand. 🤚</p>
<p>For years I have used <a href="https://kapeli.com/dash">Dash.app by Kapeli</a> to access documentation quickly offline. It is a <em>fantastic</em> app and if you spend a lot of time programming on a Mac I really recommend it. While I use it often for referring to the documentation, a surprisingly useful feature is to quickly copy the URL to documentation: I launch it, type <code>css:flex</code> and then press <code>⌘⌥-B</code> and suddenly <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/flex_value">the right link</a> is in my clipboard, ready to be sent to someone on slack or pasted into the references of a git commit.</p>
<p>I've been doing a bit more coding on linux and windows, and I missed this functionality. Fortunately <a href="https://devdocs.io/">devdocs.io</a> provides a 95% as good experience in every browser and cross-platform.</p>
<p>I made a <a href="https://github.com/evantravers/dotfiles/commit/b7e7a221747c77d6eb72898b971afc4b84087a3a">quick change to my hammerspoon setup</a>. The <a href="https://evantravers.com/articles/2020/06/08/hammerspoon-a-better-better-hyper-key/">Hyper Key</a> I had associated with Dash now switches to the devdocs.io tab or opens it if nothing is open.</p>
<p>This is a great argument for having your own "shortcut layer:" if you want to change which tool you use you can move it without losing the muscle memory.</p>
Git Spelunking to Avoid Linkrothttps://evantravers.com/articles/2024/02/20/git-spelunking-to-avoid-linkrot/2024-02-20T15:53:00+00:002024-02-20T09:57:16+00:00Evan Travers<em>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="16" width="16">
# <path fill="currentColor" d="M 4 4.44 v 2.83 c 7.03 0 12.73 5.7 12.73 12.73 h 2.83 c 0 -8.59 -6.97 -15.56 -15.56 -15.56 Z m 0 5.66 v 2.83 c 3.9 0 7.07 3.17 7.07 7.07 h 2.83 c 0 -5.47 -4.43 -9.9 -9.9 -9.9 Z M 6.18 15.64 A 2.18 2.18 0 0 1 6.18 20 A 2.18 2.18 0 0 1 6.18 15.64"></path>
</svg>
This is an RSS-only post. It's a secret! Read more about <a href="https://daverupert.com/rss-club/">RSS Club</a>.
</em><p>I have been very inconsistent with using github's permalinks in my blog posts.</p>
<p>While emailing an old post to my new friend Justin I found that the links were to the most current version of my hammerspoon config… which has long since changed. Doh!</p><em>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="16" width="16">
# <path fill="currentColor" d="M 4 4.44 v 2.83 c 7.03 0 12.73 5.7 12.73 12.73 h 2.83 c 0 -8.59 -6.97 -15.56 -15.56 -15.56 Z m 0 5.66 v 2.83 c 3.9 0 7.07 3.17 7.07 7.07 h 2.83 c 0 -5.47 -4.43 -9.9 -9.9 -9.9 Z M 6.18 15.64 A 2.18 2.18 0 0 1 6.18 20 A 2.18 2.18 0 0 1 6.18 15.64"></path>
</svg>
This is an RSS-only post. It's a secret! Read more about <a href="https://daverupert.com/rss-club/">RSS Club</a>.
</em><p>I have been very inconsistent with using github's permalinks in my blog posts.</p>
<p>While emailing an old post to my new friend Justin I found that the links were to the most current version of my hammerspoon config… which has long since changed. Doh!</p>
<p>I'm glad that I usually embed pertinent code into the blog post… that helps a lot to keep the content useful long into the future.</p>
<p>But anyway!</p>
<p>I started to try to find a regex that would represent GitHub links to my own repos that weren't permalinks. I got as far as <code>rg --pcre2 "github.com/evantravers/\w+/(?!(blob|commit|compare))"</code> but I realized I was quickly in the land of negative lookahead, and that's too much regex for me. I wound up just loading the results of <code>github.com/evantravers</code> into my nvim quickfix list via Telescope and working through it manually.</p>
<p>Thanks to stack overflow I found a <a href="https://stackoverflow.com/questions/6990484/how-to-checkout-in-git-by-date">git snippet for checking out a git repo at a certain time</a>. Using this I could time-travel in my dotfiles or hammerspoon-config repo back to the hour that I posted the blog post and grab the git commit SHA. By running <code>%s,/master/,<new sha>,g</code> in vim I replaced all the tree references with the correct point in the history, and hopefully the timeline has been saved.</p>
<p>You're welcome, Doc Brown.</p>
Configuring Yabai for Focushttps://evantravers.com/articles/2024/02/15/yabai-tiling-window-management-for-osx/2024-02-15T22:48:00+00:002024-02-20T07:21:02+00:00Evan Travers<p><img alt='' src='https://evantravers.com/images/articles/2024/02/yabai.png'></p><p>I've been aware of Yabai for a long time… but every time I gave it a whirl I was immediately greeted with an unusable spew of tiny windows. Frustrated I'd uninstall and forget about it.</p>
<p>This past month I looped back around to "maybe I should try that...</p><p><img alt='' src='https://evantravers.com/images/articles/2024/02/yabai.png'></p><p>I've been aware of Yabai for a long time… but every time I gave it a whirl I was immediately greeted with an unusable spew of tiny windows. Frustrated I'd uninstall and forget about it.</p>
<p>This past month I looped back around to "maybe I should try that tiling window manager" again… and this time it feels like it's going to stick.</p>
<h2 id="what-and-why">What and Why</h2>
<p><a href="https://github.com/koekeishiya/yabai">Yabai</a> is an OSX-only tiling window manager like i3wm. Where a normal window system has an unorganized stack of windows arranged primarily by the mouse, a tiling window manager automatically arranges your windows to maximize the screen real estate.</p>
<p>Why would you want a program to arrange your windows for you?</p>
<ul>
<li>create recognizable workspaces so that your brain is prompted to start working on a task</li>
<li>do less small arranging of windows in common tasks and workflows</li>
</ul>
<p>I have already done a fair amount of thinking and programming around this… I wrote <a href="https://github.com/evantravers/movewindows.spoon">a hammerspoon clone of spectacle or moom</a> to efficiently move windows with the keyboard. I've also written a <a href="https://github.com/evantravers/autolayout.spoon">rule-based autolayout program</a> a couple of times to set up workspaces based on system events… and <a href="https://github.com/evantravers/split.spoon">one just for splitting a screen in two</a>.</p>
<p>Within a week, Yabai has been able to supplant and exceed all of those. 🤷</p>
<h2 id="yabai">Yabai</h2>
<p>Yabai has a solid hierarchy: each <code>display</code> can have one or more <code>spaces</code> containing <code>window</code>s. When <code>layout</code> is set to <code>bsp</code>, it treats the <code>space</code> as a tree… each node can be split into two. You can <code>swap</code> the position of two windows, or <code>warp</code> a window into a tree node, then creating a new tree of two in a split.</p>
<p>Yabai has a unique design decision: the config file is just a shell script of yabai API commands. This means that reconfiguring the system is possible on the fly in lots of ways… you can have another program rewrite the config file and reload it, or just issue commands to the running instance using <code>yabai -m <command></code>. It's clever and simple.</p>
<h2 id="recommendations">Recommendations</h2>
<p>If you are going to try a tiling window manager you must both configure it to work with you and change your computer usage to match the tool's. If you have been in the habit of leaving unused windows open or unhidden (raises hand) this doesn't work well.</p>
<p>I recommend closing all your windows and starting with just one… when I first ran yabai on a pile of windows it was confusing and overwhelming. When I closed all windows but one and then started to work… its promise as a helpful focus tool started to shine.</p>
<p>I recommend using the following starter settings in your <code>~/.yabairc</code>:</p>
<div class="highlight"><pre class="highlight shell"><code><span class="c"># use the "tiled" layout that we want to try</span>
<span class="c"># (Default: float, which is "normal" window behavior.)</span>
yabai <span class="nt">-m</span> config layout bsp
<span class="c"># ctrl is the most useful mouse modifier for me…</span>
<span class="c"># I don't have fn on my mech keyboards usually.</span>
<span class="c"># (Default: fn)</span>
yabai <span class="nt">-m</span> config mouse_modifier ctrl
<span class="c"># when you drop a window on another window, stack the two.</span>
<span class="c"># (Default: swap)</span>
yabai <span class="nt">-m</span> config mouse_drop_action stack
</code></pre></div>
<p>If you ran yabai, it's now running and managing your windows! You can hold your <code>mouse_modifier</code> (now <code>ctrl</code>) and click <em>anywhere</em> on the window to move it. If you move it onto the 20% edge of a window, it'll perform a "warp" and split with that window. If you drag it onto the 20% center of the window, where it used to <code>swap</code> it'll now stack the two windows and they move together until you do a warp or close one of them.</p>
<h3 id="stacks">Stacks</h3>
<p>Stacking is the feature that made yabai usable for me. Stacking (just as it sounds) groups any number of <code>window</code>s in the same node of the tree together… and you can toggle back and forth between them as you will. There presently isn't a way to tell what is in the stack, but there is a <a href="https://github.com/AdamWagner/stackline">hammerspoon repo for StackLine</a> which is very lovely.</p>
<p>You can also set the entire <code>layout</code> to <code>stack</code>, this makes the space behave like an iphone, all apps are full screen and no splitting. I'll sometimes do this when I'm juggling a lot or I just want to focus on one thing at a time… like when writing this essay.</p>
<p>I also use stacks to group my terminal and browser when coding. New windows pop up to the side, but my main focus area stays the same. I've also stacked Microsoft Teams windows so they don't get lost, or a couple different chat apps (Signal, iMessage, etc.) so that they occupy the same place.</p>
<p>Stacking isn't really documented in the yabai wiki, but it is in the asciidoc/man-page, and the main source is <a href="https://github.com/koekeishiya/yabai/issues/203">the conversation on this pull request</a>. I hope to contribute some information to the wiki or readme over time.</p>
<h3 id="keyboard-shortcuts">Keyboard Shortcuts</h3>
<p>If you just want to use your mouse then you are set. Yabai's mouse movements are very intuitive.</p>
<p>I have a set of keyboard shortcuts I've been using for years that are heavily in my muscle memory. I use a <a href="https://evantravers.com/articles/2020/06/08/hammerspoon-a-better-better-hyper-key/">Hyper Key in Hammerspoon</a> to <a href="https://evantravers.com/articles/2019/04/03/my-keyboard-setup/#hyper-hammerspoon">launch and switch applications</a> as well as move and arrange windows, so I <a href="https://github.com/evantravers/dotfiles/blob/fde5333d673e5d0d74805739e2d4386fbf39b4c2/config/hammerspoon/init.lua#L60-L144">modified my existing module to control yabai</a>.</p>
<p>The author of yabai has another program just for sending commands using keyboard shortcuts called <a href="https://github.com/koekeishiya/skhd">skhd</a>. If you don't already have a tool for launching/changing apps or sending commands to yabai you should try that.</p>
<p>Here are binds I'm finding most helpful. (I'm going to use <code>◊</code> to mean the <a href="https://evantravers.com/articles/2020/06/08/hammerspoon-a-better-better-hyper-key/">Hyper Key</a>.)</p>
<div class="admonition">
<div class="admonition__title">Keyboard Shortcuts</div>
<dl>
<dt><strong>◊+M</strong>:</dt>
<dd>
a leader key that enters the movement modal…
<dl>
<dt><strong>1</strong>:</dt>
<dd>
<code>yabai -m window --swap first</code></br>
takes the focused window and swaps it with the “first” window in the tree… effectively putting it in the “main” location on the top left of the screen.
</dd>
<dt><strong>space</strong>:</dt>
<dd>
<code>yabai -m --toggle zoom-fullscreen</code></br>
focused window maintains it’s “virtual” position in the tree, but zooms to full screen.
</dd>
<dt><strong>V</strong>:</dt>
<dd>
<code>yabai -m space --mirror y-axis</code></br>
mirror space vertically.
</dd>
<dt><strong>X</strong>:</dt>
<dd>
<code>yabai -m window --toggle split</code></br>
swap vertical/horizontal split for a tree.
</dd>
<dt><strong>T</strong>:</dt>
<dd>
<code>yabai -m window --ratio 0.6</code></br>
set current window's split ratio to 2/3rds of the tree.
</dd>
<dt><strong>S</strong>:</dt>
<dd>
<code>yabai -m window --stack mouse</code></br>
focused window stacks itself with the window currently under the mouse cursor.
</dd>
<dt><strong>⇧-B</strong>:</dt>
<dd>
<code>yabai -m space --layout stack</code></br>
focused space (current monitor) handles all windows as a stack.
</dd>
<dt><strong>B</strong>:</dt>
<dd>
<code>yabai -m space --layout bsp</code></br>
focused space handles all windows as tiles.
</dd>
<dt><strong>H/J/K/L</strong>:</dt>
<dd>
<code>yabai -m window --swap west/south/north/east</code></br>
focused window swaps position using vim-directions.
</dd>
<dt><strong>⌥-H/J/K/L</strong>:</dt>
<dd>
<code>yabai -m window --warp west/south/north/east</code></br>
focused window warps position using vim-directions.
</dd>
<dt><strong>⇧-H/L</strong>:</dt>
<dd>
<code>yabai -m window --display west/east</code></br>
focused window shifts to another space using vim-directions.
</dd>
</dl>
</dd>
</dl>
</div>
<p>If you want to see <a href="https://github.com/evantravers/dotfiles/blob/c1d4d184194a75f065f97357ad656b0eece5126f/config/hammerspoon/init.lua#L60-L144">the hammerspoon code it's here</a>, thanks to @dmitriiminaev on github for <a href="https://github.com/dmitriiminaev/Hammerspoon-Yabai/blob/master/.hammerspoon/yabai.lua">the hs.task implementation</a>.</p>
<p>I have rules in my .yabairc for programs that don't work well with yabai:</p>
<div class="highlight"><pre class="highlight shell"><code>yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s1">'System Settings'</span> <span class="nv">manage</span><span class="o">=</span>off
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s1">'Timery'</span> <span class="nv">manage</span><span class="o">=</span>off
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s1">'Cardhop'</span> <span class="nv">manage</span><span class="o">=</span>off
</code></pre></div>
<p>And a few applications that I want to default open on my laptop monitor (if present):</p>
<div class="highlight"><pre class="highlight shell"><code>yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s1">'Fantastical'</span> <span class="nv">display</span><span class="o">=</span>east
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s1">'OBS'</span> <span class="nv">display</span><span class="o">=</span>east
</code></pre></div>
<h2 id="daily-usage">Daily Usage</h2>
<p>I care about focus: spending my time on the main thing as much as possible, and not being distracted. Yabai has become my little window butler… when something other than my main window arrives, yabai sidles up with the window on a plate, ready for my attention.</p>
<p>Most of the time I'm focused on a single task and using one program to accomplish this: writing in Obsidian, designing in Figma, having a conversation in Teams. If I launch a browser to look up some documentation or to find a previous blog post, it opens in the previously defined split until I dismiss it.<sup id="fnref1"><a href="#fn1">1</a></sup></p>
<p>Opening a window to grab a link from my blog, research a topic, or send a quick message and then closing/hiding<sup id="fnref2"><a href="#fn2">2</a></sup> it has become second nature. If there is too much going on at once, I can always disable BSP entirely and use the <code>stack</code> <code>layout</code> to emulate my previous "everything is full screen" setup. It's been great. While I have a lot of tools to move and position windows… I find that I'm using them <em>much</em> less.</p>
<h2 id="part-b8a856d">…</h2>
<p>I'll be the first to admit that I change my digital environment easily and often… but I do think that yabai has added quite a bit to my daily workflow. 90% of the time new windows open logically in a useful place, and if I want to create some order out of the chaos it's a few natural mouse swipes away. A logical and simple scripting API means that more complex and interesting setups are possible (I'm thinking about detecting a video call window and setting some new <code>yabai -m rule</code> to match) but for now it just works.</p>
<p>Related:</p>
<ul>
<li><a href="https://bryce-s.com/yabai/">Bryce's writeup has great gifs and information I didn't know</a></li>
</ul>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p><strike>One complaint I have is that I haven't been able to resize a window just using the <code>yabai -m</code> commands… I can hold <code>^</code> and right-click drag a window to resize it fast, but I would really like to be able to easily say "I want my main window to be 2/3 of the screen." Someday soon.</strike> <a href="https://github.com/evantravers/dotfiles/commit/d3f2dc3e18d71f51a46853402fc779ab17102116">I have been able to do this</a>. I found <a href="https://github.com/koekeishiya/yabai/blob/af9d4cd3a96a7cfa02df1ff61d89414f5259b06e/doc/yabai.asciidoc?plain=1#L345-L346">the <code>window --ratio</code> options in the asciidoc</a>, that works wonderfully. <a href="#fnref1">↩</a></p>
</li>
<li id="fn2">
<p>I may override the default OSX hiding animation with <code>hs.window():hide()</code> to speed that up! <a href="#fnref2">↩</a></p>
</li>
</ol>
</div>
One Must Imagine Sisyphus Overwhelmedhttps://evantravers.com/articles/2024/02/12/one-must-imagine-sisyphus-overwhelmed/2024-02-12T17:19:00+00:002024-02-20T09:38:14+00:00Evan Travers<em>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="16" width="16">
# <path fill="currentColor" d="M 4 4.44 v 2.83 c 7.03 0 12.73 5.7 12.73 12.73 h 2.83 c 0 -8.59 -6.97 -15.56 -15.56 -15.56 Z m 0 5.66 v 2.83 c 3.9 0 7.07 3.17 7.07 7.07 h 2.83 c 0 -5.47 -4.43 -9.9 -9.9 -9.9 Z M 6.18 15.64 A 2.18 2.18 0 0 1 6.18 20 A 2.18 2.18 0 0 1 6.18 15.64"></path>
</svg>
This is an RSS-only post. It's a secret! Read more about <a href="https://daverupert.com/rss-club/">RSS Club</a>.
</em><p>"What's going on?" My wife asked me last night. "I'm not sure" I replied. "I can't put my finger on it, I feel like I'm forgetting something."</p>
<p>I feel asleep still not sure, roused too often by the midnight auditory hallucination of my alarm.</p>
<h2 id="part-b8a856d">…</h2>
<em>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="16" width="16">
# <path fill="currentColor" d="M 4 4.44 v 2.83 c 7.03 0 12.73 5.7 12.73 12.73 h 2.83 c 0 -8.59 -6.97 -15.56 -15.56 -15.56 Z m 0 5.66 v 2.83 c 3.9 0 7.07 3.17 7.07 7.07 h 2.83 c 0 -5.47 -4.43 -9.9 -9.9 -9.9 Z M 6.18 15.64 A 2.18 2.18 0 0 1 6.18 20 A 2.18 2.18 0 0 1 6.18 15.64"></path>
</svg>
This is an RSS-only post. It's a secret! Read more about <a href="https://daverupert.com/rss-club/">RSS Club</a>.
</em><p>"What's going on?" My wife asked me last night. "I'm not sure" I replied. "I can't put my finger on it, I feel like I'm forgetting something."</p>
<p>I feel asleep still not sure, roused too often by the midnight auditory hallucination of my alarm.</p>
<h2 id="part-b8a856d">…</h2>
<p>I get this way when I have too many irons in the fire… too many conversations, too many side projects, too many drafts brewing in my digital garden.</p>
<p>Of course, writing helps. So this morning I start a list… it's healthy, right? Admitting I get moody because I'm a mortal creature without the capability to fix everything I see. Remembering I'm not the creator of the cosmos. It won't be a todo list, it'll be a confession!</p>
<p>Between the moment I woke up and put on my jacket and grabbed my pack, here are the things that flitted through my awareness:</p>
<ul>
<li>that closet that's 75% been cataloged and reorganized, but I had to shove it all back in again and now it's 150% worse</li>
<li>Bushes that need to be cut back off my fence</li>
<li>A self hosted system for storing and syncing contacts because Google has failed me for the last time</li>
<li>a season of heavy change at the day job</li>
<li>A set of scripts and markdown queries to emulate Things in Obsidian Tasks</li>
<li>A doctors appointment for a foot injury that won't heal</li>
<li>The infernal blog transition that I've been piddling on for years</li>
<li>a fourth rewrite of my nix dotfiles<sup id="fnref1"><a href="#fn1">1</a></sup></li>
<li>essays on:
<ul>
<li>UX and Sin in VR/AR</li>
<li>my position on AI, surveillance capitalism, and the trust apocalypse</li>
<li>Defrauding and the ethics of LLMs</li>
<li>a Biblical position on burnout</li>
<li>Window management and Yabai</li>
<li>My current Obsidian usage</li>
<li>The miracle of nix modules</li>
<li>Half a dozen scraps of thought around creativity systems</li>
</ul></li>
<li>The outline of a book on journaling</li>
<li>taxes</li>
</ul>
<p>It's too much!</p>
<p>I am <a href="https://evantravers.com/articles/2019/10/09/sisyphus-and-the-focus-boulder/">once again Sisyphus</a>, and you must imagine I am not happy.</p>
<p>Two things I must do… I have to wrangle all the things floating around in my head and put them somewhere safe. Then I need to ruthlessly cut this list.</p>
<p>And this little article is a start.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>I did knock this over this evening: https://github.com/evantravers/dotfiles/blob/fde5333d673e5d0d74805739e2d4386fbf39b4c2/flake.nix <a href="#fnref1">↩</a></p>
</li>
</ol>
</div>
Switching to nix-darwin and Flakeshttps://evantravers.com/articles/2024/02/06/switching-to-nix-darwin-and-flakes/2024-02-06T18:14:00+00:002024-02-06T12:17:34+00:00Evan Travers<p><img alt='' src='https://evantravers.com/images/articles/2024/02/nix-darwin.jpeg'></p><p>Since my last post about <a href="https://evantravers.com/articles/2023/11/28/moving-my-dotfiles-to-nix/">using Nix to configure my dotfiles</a>, I've since moved to using it for <em>everything</em>.<sup id="fnref1"><a href="#fn1">1</a></sup> (Sort of.)</p>
<p>In my pursuit of treating my computer as <a href="https://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/">cattle not as a pet</a> I have had a series of increasingly complicated systems to help me set...</p><p><img alt='' src='https://evantravers.com/images/articles/2024/02/nix-darwin.jpeg'></p><p>Since my last post about <a href="https://evantravers.com/articles/2023/11/28/moving-my-dotfiles-to-nix/">using Nix to configure my dotfiles</a>, I've since moved to using it for <em>everything</em>.<sup id="fnref1"><a href="#fn1">1</a></sup> (Sort of.)</p>
<p>In my pursuit of treating my computer as <a href="https://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/">cattle not as a pet</a> I have had a series of increasingly complicated systems to help me set up a new computer after a reset<sup id="fnref2"><a href="#fn2">2</a></sup>. The earliest I can find is over a decade old… a list of tools I use so that I wouldn't lose it.<sup id="fnref3"><a href="#fn3">3</a></sup> Later on I had a git repo of common config files and a complicated README on how to set it up. As of last year, that had progressed to a combination of <code>brew bundle</code> a <a href="https://github.com/evantravers/dotfiles/blob/before-nix/Brewfile">Brewfile</a> to install applications and dependencies, <a href="https://github.com/evantravers/dotfiles/blob/before-nix/macos">macos.sh</a> to set up common settings, and a <a href="https://github.com/evantravers/dotfiles/blob/before-nix/Makefile"><code>Makefile</code></a> to run it all. (You can see <a href="https://github.com/evantravers/dotfiles/tree/before-nix">that version in git history</a>.)</p>
<p>After moving my dotfiles to nix and learning some more… I wanted to try <a href="https://daiderd.com/nix-darwin/">nix-darwin</a>. nix-darwin is a module system for configuring a Mac system.</p>
<p>Because I had embraced home-manager first, I had to do some refactoring.</p>
<ol>
<li>I wrote a nix-darwin configuration first… got that working.</li>
<li>I then refactored my home-manager to not be the "entry point" into my nix world.</li>
<li>I then wrote nix-darwin and nixos config to drive home-manager, so that it's a top-down configuration.</li>
</ol>
<p>It took some mangling, but it feels great. For the first time in years, if I type <code>brew</code> into my mac terminal it doesn't know what <code>brew</code> is. (It's actually still there, but more on that later)</p>
<h2 id="nix-darwin">nix-darwin</h2>
<p>At first I just used the module in the default location of the configuration file until I got it working. Eventually I rewrote it without host-specific values and used it in the flake, which I'll show in a minute.</p>
<div class="highlight"><pre class="highlight nix"><code><span class="p">{</span> <span class="nv">config</span><span class="p">,</span> <span class="nv">pkgs</span><span class="p">,</span> <span class="err">…</span> <span class="p">}:</span>
<span class="p">{</span>
<span class="nv">environment</span><span class="o">.</span><span class="nv">systemPackages</span> <span class="o">=</span>
<span class="p">[</span>
<span class="nv">pkgs</span><span class="o">.</span><span class="nv">home-manager</span>
<span class="p">];</span>
<span class="c"># Use a custom configuration.nix location.</span>
<span class="nv">environment</span><span class="o">.</span><span class="nv">darwinConfig</span> <span class="o">=</span> <span class="s2">"$HOME/src/github.com/evantravers/dotfiles/nix-darwin-configuration"</span><span class="p">;</span>
<span class="c"># Auto upgrade nix package and the daemon service.</span>
<span class="nv">services</span><span class="o">.</span><span class="nv">nix-daemon</span><span class="o">.</span><span class="nv">enable</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nv">nix</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">package</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">nix</span><span class="p">;</span>
<span class="nv">settings</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"extra-experimental-features"</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">"nix-command"</span> <span class="s2">"flakes"</span> <span class="p">];</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="c"># Create /etc/zshrc that loads the nix-darwin environment.</span>
<span class="nv">programs</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">gnupg</span><span class="o">.</span><span class="nv">agent</span><span class="o">.</span><span class="nv">enable</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nv">zsh</span><span class="o">.</span><span class="nv">enable</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="c"># default shell on catalina</span>
<span class="p">};</span>
<span class="c"># Used for backwards compatibility, please read the changelog before changing.</span>
<span class="c"># $ darwin-rebuild changelog</span>
<span class="nv">system</span><span class="o">.</span><span class="nv">stateVersion</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
<span class="c"># Install fonts</span>
<span class="nv">fonts</span><span class="o">.</span><span class="nv">fontDir</span><span class="o">.</span><span class="nv">enable</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nv">fonts</span><span class="o">.</span><span class="nv">fonts</span> <span class="o">=</span> <span class="p">[</span>
<span class="nv">pkgs</span><span class="o">.</span><span class="nv">monaspace</span>
<span class="p">];</span>
<span class="c"># Use homebrew to install casks and Mac App Store apps</span>
<span class="nv">homebrew</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">enable</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nv">casks</span> <span class="o">=</span> <span class="p">[</span>
<span class="s2">"1password"</span>
<span class="s2">"bartender"</span>
<span class="s2">"brave-browser"</span>
<span class="s2">"fantastical"</span>
<span class="s2">"firefox"</span>
<span class="s2">"hammerspoon"</span>
<span class="s2">"karabiner-elements"</span>
<span class="s2">"obsidian"</span>
<span class="s2">"raycast"</span>
<span class="s2">"soundsource"</span>
<span class="s2">"wezterm"</span>
<span class="p">];</span>
<span class="nv">masApps</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"Drafts"</span> <span class="o">=</span> <span class="mi">1435957248</span><span class="p">;</span>
<span class="s2">"Reeder"</span> <span class="o">=</span> <span class="mi">1529448980</span><span class="p">;</span>
<span class="s2">"Things"</span> <span class="o">=</span> <span class="mi">904280696</span><span class="p">;</span>
<span class="s2">"Timery"</span> <span class="o">=</span> <span class="mi">1425368544</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="c"># set some OSX preferences that I always end up hunting down and changing.</span>
<span class="nv">system</span><span class="o">.</span><span class="nv">defaults</span> <span class="o">=</span> <span class="p">{</span>
<span class="c"># minimal dock</span>
<span class="nv">dock</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">autohide</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nv">orientation</span> <span class="o">=</span> <span class="s2">"left"</span><span class="p">;</span>
<span class="nv">show-process-indicators</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="nv">show-recents</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="nv">static-only</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">};</span>
<span class="c"># a finder that tells me what I want to know and lets me work</span>
<span class="nv">finder</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">AppleShowAllExtensions</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nv">ShowPathbar</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nv">FXEnableExtensionChangeWarning</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">};</span>
<span class="c"># Tab between form controls and F-row that behaves as F1-F12</span>
<span class="nv">NSGlobalDomain</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">AppleKeyboardUIMode</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
<span class="s2">"com.apple.keyboard.fnState"</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="p">}</span>
</code></pre></div>
<p>(source: <a href="https://github.com/evantravers/dotfiles/blob/4e9bc7a25ebc73389130567ab46b9cab78b5783e/nix-darwin-configuration/darwin.nix">darwin.nix</a>)</p>
<p>Pretty soon I could run <code>darwin-rebuild switch</code> and it'd flow right on through!</p>
<h2 id="flakes">Flakes</h2>
<p>I made my jump to nix-darwin a little bit more complicated by deciding to go all in on <a href="https://nixos.wiki/wiki/Flakes">nix-flakes</a>. I made this decision because I wanted the reproducability of a flake for configuring a computer. Without flakes the configuration would be at the mercy of the host system's channel configuration… and I want to be a little more careful. (and I want to learn about flakes!)</p>
<p>I used the flake generators from each project, then I modified heavily from examples I found online. (Searching google for "nix-darwin examples" is a lot less helpful than searching github's code search, FYI!)</p>
<p>The final flake (simplified and over-documented) looks something like this:</p>
<div class="highlight"><pre class="highlight nix"><code><span class="p">{</span>
<span class="nv">description</span> <span class="o">=</span> <span class="s2">"Evan's darwin system"</span><span class="p">;</span>
<span class="nv">inputs</span> <span class="o">=</span> <span class="p">{</span>
<span class="c"># I pinned darwin to a particular release</span>
<span class="nv">nixpkgs</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"github:NixOS/nixpkgs/nixpkgs-23.11-darwin"</span><span class="p">;</span>
<span class="c"># I then pinned home-manager so that it would not issue the mismatch error</span>
<span class="nv">home-manager</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"github:nix-community/home-manager/release-23.11"</span><span class="p">;</span>
<span class="nv">home-manager</span><span class="o">.</span><span class="nv">inputs</span><span class="o">.</span><span class="nv">nixpkgs</span><span class="o">.</span><span class="nv">follows</span> <span class="o">=</span> <span class="s2">"nixpkgs"</span><span class="p">;</span>
<span class="nv">nix-darwin</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"github:LnL7/nix-darwin"</span><span class="p">;</span>
<span class="nv">nix-darwin</span><span class="o">.</span><span class="nv">inputs</span><span class="o">.</span><span class="nv">nixpkgs</span><span class="o">.</span><span class="nv">follows</span> <span class="o">=</span> <span class="s2">"nixpkgs"</span><span class="p">;</span>
<span class="p">};</span>
<span class="nv">outputs</span> <span class="o">=</span> <span class="nv">inputs</span><span class="o">@</span><span class="p">{</span> <span class="nv">self</span><span class="p">,</span> <span class="nv">nix-darwin</span><span class="p">,</span> <span class="nv">home-manager</span><span class="p">,</span> <span class="nv">nixpkgs</span> <span class="p">}:</span> <span class="p">{</span>
<span class="nv">darwinConfigurations</span> <span class="o">=</span> <span class="p">{</span>
<span class="c"># I have couple computers, but one config per</span>
<span class="s2">"Evans-MacBook-Pro"</span> <span class="o">=</span> <span class="nv">nix-darwin</span><span class="o">.</span><span class="nv">lib</span><span class="o">.</span><span class="nv">darwinSystem</span> <span class="p">{</span>
<span class="nv">system</span> <span class="o">=</span> <span class="s2">"x86_64-darwin"</span><span class="p">;</span> <span class="c"># alternatively "aarch64-darwin"</span>
<span class="nv">modules</span> <span class="o">=</span> <span class="p">[</span>
<span class="c"># include the darwin module</span>
<span class="sx">./darwin.nix</span>
<span class="c"># setup home-manager</span>
<span class="nv">home-manager</span><span class="o">.</span><span class="nv">darwinModules</span><span class="o">.</span><span class="nv">home-manager</span>
<span class="p">{</span>
<span class="nv">home-manager</span> <span class="o">=</span> <span class="p">{</span>
<span class="c"># include the home-manager module</span>
<span class="nv">users</span><span class="o">.</span><span class="nv">evan</span> <span class="o">=</span> <span class="kr">import</span> <span class="sx">../home-manager/home.nix</span><span class="p">;</span>
<span class="p">};</span>
<span class="nv">users</span><span class="o">.</span><span class="nv">users</span><span class="o">.</span><span class="nv">evan</span><span class="o">.</span><span class="nv">home</span> <span class="o">=</span> <span class="s2">"/Users/evan"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">];</span>
<span class="nv">specialArgs</span> <span class="o">=</span> <span class="p">{</span> <span class="kn">inherit</span> <span class="nv">inputs</span><span class="p">;</span> <span class="p">};</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="p">}</span>
</code></pre></div>
<p>(source: <a href="https://github.com/evantravers/dotfiles/blob/4e9bc7a25ebc73389130567ab46b9cab78b5783e/nix-darwin-configuration/flake.nix">flake.nix</a>)</p>
<p>I did some folder re-arranging. A year ago I <a href="https://github.com/evantravers/dotfiles/blob/before-nix/">had a series of folders</a> that corresponded to the programs and XDG paths I was configuring. I carried forward that mentality to the first version of nix when I used home-manager, where I symlinked the whole folder to <code>~/.config/home-manager</code>. Now, I just run the nix command against the folder in my <code>~/src</code> directory. No symlinks required.</p>
<p>I believe there is a way to <a href="https://determinate.systems/posts/nix-run">run a flake from a git repo with no installation steps</a>. It's possible that I could run something akin to <code>nix run github:evantravers/dotfiles?dir=nix-darwin-configuration</code>… but I need to do some research on that first.<sup id="fnref4"><a href="#fn4">4</a></sup> It's exciting though!</p>
<p>Besides the horde of <code>;</code> and <code>{}: {}</code> problems (read: my lack of experience with the language forms,) I ran into one issue where I needed to specify <code>users.users.<name>.home</code> because of a <a href="https://github.com/nix-community/home-manager/issues/4026">home-manager bug</a>.</p>
<h2 id="homebrew-for-apps">Homebrew for Apps</h2>
<p>I wrestled back and forth with this. @teoljungberg <a href="https://github.com/teoljungberg/dotfiles/blob/ffde67e8b7f650fbf1e882d2e6c699fd9ba428f6/nixpkgs/hammerspoon.nix">demo'd how to download hammerspoon from the online release</a>, but I opted for what @jonathandion did and <a href="https://github.com/jonathandion/nix-config/blob/1fde611300cf0c39664a70c544c7bb7375c3b496/darwin-configuration.nix#L42">used homebrew to install hammerspoon</a> I may write a couple of such expressions to get some of my favorite applications that neither have casks nor are on the Mac App Store, <a href="https://www.homerow.app/">Homerow</a> and <a href="https://goodsnooze.gumroad.com/l/macwhisper">MacWhisper</a> for example.</p>
<p>Homebrew is no longer in my path... but it is a <em>very</em> convenient way to get certain packages, and it'll probably stay that way. I will <em>not</em> use it to get things that home-manager can manage though. Mac-specific packages.</p>
<h2 id="part-b8a856d">…</h2>
<p>I did a very similar process to configure nixos as a flake, and now I have a single home-manager system that is shared equally between the two OS configurations.</p>
<p>At the moment, home-manager isn't standalone anymore. I can't run it without running a <code>darwin-rebuild switch</code> or <code>nixos-rebuild switch</code> command. That's not bugging me, but it does seem kind of weird that the only way to get <a href="https://github.com/roosta/tmux-fuzzback">a new tmux plugin</a> is to rebuild the universe from first principles.</p>
<p>It also feels very weird to no longer structure my repo based on <code>stow</code>/symlink assumptions, but it dodges <em>so</em> many headaches and simplifies rolling back changes.</p>
<p>I also want to mess with home-manager apparently has <a href="https://mipmip.github.io/home-manager-option-search/?query=accounts">options for setting up calendars and emails</a>… I want to know all about that… but I may need some extra privacy for those.</p>
<p>I'm sure that the first time I set up a new mac/PC from scratch I'll revisit this and have to change a few things, but I'm excited to have something so robust to lean on next time.</p>
<h3 id="references">References:</h3>
<ul>
<li><a href="https://daiderd.com/nix-darwin/manual/index.html">Nix Darwin Manual</a> and <a href="https://daiderd.com/nix-darwin/manual/index.html">Options Documentation</a></li>
<li><a href="https://github.com/thexyno/nixos-config">@thexyno's configuration</a> had a lot of useful tricks.</li>
</ul>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>There's a lot of buzz about nix on the orange site and red site… and people have accurately nix a cult. I think it is suffering from <a href="https://tvtropes.org/pmwiki/pmwiki.php/Main/WhenAllYouHaveIsAHammer">"new hammer"</a> problems. I am persuaded that it's the best stab at solving some common problems I have in computing… so I'm drinking the koolaid. <a href="#fnref1">↩</a></p>
</li>
<li id="fn2">
<p>A reset could be a catastrophic hardware failure, a new device being issued by the company, or me just munging up my paths and <code>brew update</code> so bad that it's faster to start from scratch than undo my foolishness. Ask me how I know. <a href="#fnref2">↩</a></p>
</li>
<li id="fn3">
<p>I <em>know</em> you are curious… <a href="https://gist.github.com/evantravers/545f9bf594cb3c79f15d14322bceaf32">here's my list from many years ago</a>. This was pre-vim, so I'm thinking ~2009? I think Square was new, and I was an early adopter. <a href="#fnref3">↩</a></p>
</li>
<li id="fn4">
<p>I think I need to make a master flake.nix to route the command to the right place. <a href="https://github.com/nmasur/dotfiles/blob/master/flake.nix">@nmasur has an example</a>. <a href="#fnref4">↩</a></p>
</li>
</ol>
</div>
Digital Prayer Journal using Contactshttps://evantravers.com/articles/2024/02/02/digital-prayer-journal-using-contacts/2024-02-02T16:02:00+00:002024-02-02T10:09:53+00:00Evan Travers<p>This is another one of those systems that has grown with me over the years.</p>
<p>Years ago, I began simply keeping a list of prayer requests in Reminders.app and glancing at it periodically before praying.</p>
<p>Some time later I was feeling a bit despondent...</p><p>This is another one of those systems that has grown with me over the years.</p>
<p>Years ago, I began simply keeping a list of prayer requests in Reminders.app and glancing at it periodically before praying.</p>
<p>Some time later I was feeling a bit despondent. I felt the weight of the endless list of prayer requests, and lamented that I felt no answers… then I pressed the "show completed tasks". I teared up as I scrolled through years of requests that I had checked off because they were answered. I was greatly encouraged… and kept the habit.</p>
<p>Some time later, I stopped using facebook. Without the universal spying CRM of facebook I had no way to keep track of who is related to whom, when were birthdays coming up, all that sort of thing. I started to diligently update my Google contacts database. This let me remember family birthdays, which of my friends was studying data science, where people work, etc.<sup id="fnref1"><a href="#fn1">1</a></sup></p>
<p>For the past two years I've combined both the prayer list and personal CRM in my contacts. This has grown into an evolving practice that brings me a lot of joy and purpose.</p>
<h2 id="what-now">What now?</h2>
<h3 id="recording">Recording</h3>
<p>When I'm talking to someone 1:1 I'll either write in my journal (if appropriate) or afterward make a quick note in <a href="https://evantravers.com/articles/tags/draftsapp/">Drafts</a> of the form:</p>
<div class="highlight"><pre class="highlight markdown"><code>Person's name
<span class="p">
-</span> bullet points
<span class="p">-</span> about what we talked about
<span class="p">-</span> prayer: thinking about parenting and the future
</code></pre></div>
<p>At the end of the day as I'm cleaning my Drafts inbox I run a <a href="https://evantravers.com/articles/2021/02/02/drafts-to-cardhop/">venerable Drafts shortcut</a> to send the update to Cardhop<sup id="fnref2"><a href="#fn2">2</a></sup>. Cardhop's default makes it easy to find things like "who did I talk to recently that is studying bioinformatics?" or "What was her maiden name?" I have a pretty good habit of recording those "things that I'd like to remember later," which is by far the hardest part of this.</p>
<h3 id="remembering">Remembering</h3>
<p>I don't yet have a solid system for paging through requests. Cardhop does have a "recently updated" page that makes it easy to see people I've talked to recently, but for a while I've been just doing a lightweight memory trick: I associate some prayer requests with common things in my life:</p>
<ul>
<li>tying my barefoot shoes reminds me to pray for my pastor friend who also likes barefoot shoes.</li>
<li>woodworking tools reminds me to pray for a friend who enjoys woodworking</li>
<li>using my Obsidian bible reminds me to pray for a friend who got me into using that tool</li>
</ul>
<p>This habit helps my memory and keeps me praying nearly the whole day.</p>
<h3 id="celebrating">Celebrating</h3>
<p>Since I journal and digitally log important conversations, it's pretty easy to cruise through my 13 weekly summary pages and yank out all the answers to prayer on a quarterly basis… which can then get pushed up to a yearly page. God is not a magic machine that responds to hours spent on knees, but seeing that he has a plan and seeing his goodness is a great encouragement whenever I look through such a list.</p>
<h2 id="part-b8a856d">…</h2>
<p>This practice can look different for everyone. Index cards, a bullet journal... it doesn't matter. The practice trains our minds to reach to prayer first and tunes our hearts to recognize God's power in our daily lives.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>Stephen Wolfram has a really exaggerated and 40-year old version of this <a href="https://evantravers.com/articles/2019/03/31/books-and-links-march/#seeking-the-productive-life-some-details-of-my-personal-infrastructure-stephen">I blogged about earlier</a>. I'd like to move my contacts off of Google Contacts to something more stable… I don't know what that looks like yet. I wish I could track my contacts in git and use plain text… and still have access to it on my phone… hmm. <a href="#fnref1">↩</a></p>
</li>
<li id="fn2">
<p><a href="https://flexibits.com/cardhop">Cardhop</a> is great… but I'm syncing with Google Contacts, and I feel like something sometimes gets lost in migration/translation. I <em>know</em> I lose content sometimes, and my system keeps forgetting birthdays and anniversaries. A friend just jogged my memory about <a href="https://www.monicahq.com/">Monica HQ</a>, I'm going to see if I can get that setup on my home server. <a href="#fnref2">↩</a></p>
</li>
</ol>
</div>
Searching Amazon for A Specific Screwhttps://evantravers.com/articles/2024/01/13/searching-amazon-for-a-specific-screw/2024-01-14T02:40:00+00:002024-01-13T20:50:28+00:00Evan Travers<div class="admonition tldr">
<p>You can use Amazon's photo search app together with a ruler or penny to find precisely sized objects that are otherwise hard to describe.</p>
</div>
<p>In 2018 Amazon<sup id="fnref1"><a href="#fn1">1</a></sup> <a href="https://techcrunch.com/2018/07/19/amazons-new-ar-part-finder-helps-you-shop-for-those-odd-nuts-and-bolts/">released a camera search tool</a> to help homeowners and businesses identify mechanical parts.</p>
...<div class='admonition tldr'><p>You can use Amazon's photo search app together with a ruler or penny to find precisely sized objects that are otherwise hard to describe.</p>
</div>
<p>In 2018 Amazon<sup id="fnref1"><a href="#fn1">1</a></sup> <a href="https://techcrunch.com/2018/07/19/amazons-new-ar-part-finder-helps-you-shop-for-those-odd-nuts-and-bolts/">released a camera search tool</a> to help homeowners and businesses identify mechanical parts.</p>
<p>Part Finder is no longer an option in the current Amazon iOS app. It's been absorbed into their fashion-focused Amazon Lens tool, primarily marketed as a slick way to figure out and purchase a copy of a garment.</p>
<p>While there is another <a href="https://www.amazon.com/s?k=screws&crid=1UKGNSIGYXDUB&sprefix=screws%2Caps%2C138&ref=nb_sb_noss_1">custom search filter</a> for narrowing a hardware search, I think the original Part Finder code is still hidden in there and <em>very</em> useful… especially if you don't know what this doohickey is called.</p>
<p>It still works the same: take a picture of the object. While they don't instruct it anymore, it seems like adding a ruler or a common object (the original feature used US coins) helps the system size it accurately.</p>
<p>I can't tell you how many times I searched for this little doohickey. It attaches a frame (apparently called a valance) to our windows. Some of them had broken, and I was looking for a replacement… and I could <em>not</em> think of the right words to google. Finally used Amazon Lens, and I had 15 replacements the next day.</p>
<figure class="two-up">
<img src="/images/articles/2024/01/clip_1.png">
<img src="/images/articles/2024/01/clip_2.png">
</figure>
<p>That third option for this guy is 100% correct.</p>
<figure class="two-up">
<img src="/images/articles/2024/01/bolt_1.png">
<img src="/images/articles/2024/01/bolt_2.png">
</figure>
<p>Using a coin like a penny can add more accuracy. I've found it to be pretty reliable at identifying length and even diameter of bolts and screws. (In this case, the second result is 100% accurate.)</p>
<figure class="two-up">
<img src="/images/articles/2024/01/screw_1.png">
<img src="/images/articles/2024/01/screw_2.png">
</figure>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>I would prefer to support small businesses if I can… but sometimes it's easier to just use the jungle app. <a href="#fnref1">↩</a></p>
</li>
</ol>
</div>
Quarterly Projectshttps://evantravers.com/articles/2024/01/08/quarterly-projects/2024-01-08T15:44:00+00:002024-01-08T10:14:21+00:00Evan Travers<p>A friend wrote…</p>
<div class="admonition chat_in">
<div class="admonition__title">Joschua</div>
<p>Unrelated: I’d love to read about your Things project setup at some point! Looks like they are more time-bound 12WY-ish areas. I’m curious.</p>
</div>
<div class="admonition chat_out">
<div class="admonition__title">Me</div>
<p>I could have sworn I wrote something about it, but it appears to be gone. Consider...</p>
</div><p>A friend wrote…</p>
<div class='admonition chat_in'><div class='admonition__title'>Joschua</div><p>Unrelated: I’d love to read about your Things project setup at some point! Looks like they are more time-bound 12WY-ish areas. I’m curious.</p>
</div>
<div class='admonition chat_out'><div class='admonition__title'>Me</div><p>I could have sworn I wrote something about it, but it appears to be gone. Consider me prompted.</p>
</div>
<p>Projects are defined as outcomes that will require more than one action step to complete and that you can mark off as finished in the next 12 months.<sup id="fnref1"><a href="#fn1">1</a></sup> That makes sense and most of our work falls into this category: Write this blog post, fix the cabinets, file our taxes. In a traditional GTD<sup id="fnref2"><a href="#fn2">2</a></sup> sense we can group our Projects into the Areas of our life… traditionally location or role bound constraints where we partition our focus depending on where we are standing.</p>
<p>Where then does "working on our house" go? As my system has grown there's been a couple "everlasting" projects that require more than one action step. Progress is possible… but they are never "done." Examples: Date Nights. Yardwork. House Chores. Financial Stewardship. Important stuff!</p>
<p>Things.app works better when your tasks are in Projects (Sections, searching, etc.) so I tended to have "everlasting" projects that were just buckets of tasks on a project that never ended.</p>
<p>As the years wore on, Projects were moving and celebrated but the Everlasting Projects felt immovable. It felt like no progress was being made. No accomplishments to celebrate, no moments to reconsider assumptions… they ballooned and swelled to an unhealthy size.</p>
<p>Sometime in early 2020 I decided to change this practice.</p>
<p>For three years now have been constraining my "everlasting" projects into <a href="https://evantravers.com/articles/2021/05/29/review-12-week-year/">12WY</a> style quarters. On <a href="https://evantravers.com/articles/2023/11/13/rituals-circa-2023/#12wy-every-12-weeks">my quarterly planning</a> I close each out, creating the next iteration. (<code>House Q1</code> -> <code>House Q2</code>). The migration of tasks and chores between them is quite like the "<a href="https://evantravers.com/articles/2021/11/02/the-bullet-journal-method-by-ryder-carroll/">bullet journal</a> migration" practice, affording the moment to ask "do I want to keep this?" I also look back on three months of accomplished chores and tasks and celebrate that while it's never done, it was stewarded appropriately.</p>
<p>If you are bullet journaling, you are doing this already. If you have a digital project that will never go away and feels <a href="https://evantravers.com/articles/2019/10/09/sisyphus-and-the-focus-boulder/">Sisyphean</a>, give this a try.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>Definition from <a href="https://gettingthingsdone.com/2017/05/managing-projects-with-gtd/">gettingthingsdone.com</a> <a href="#fnref1">↩</a></p>
</li>
<li id="fn2">
<p>I still haven't read that book. 🤦 <a href="#fnref2">↩</a></p>
</li>
</ol>
</div>
iOS Homescreen 2024https://evantravers.com/articles/2024/01/05/ios-homescreen-2024/2024-01-05T14:47:00+00:002024-01-08T08:28:23+00:00Evan Travers<p>The <a href="https://evantravers.com/articles/2022/01/12/ios-homescreen-2022/">homescreen I described last time</a> has stayed pretty relevant for a long time, but the most recent iOS update added some new features and so it has evolved. It's essentially all the same functionality and applications, just moved around a little...</p><p>The <a href="https://evantravers.com/articles/2022/01/12/ios-homescreen-2022/">homescreen I described last time</a> has stayed pretty relevant for a long time, but the most recent iOS update added some new features and so it has evolved. It's essentially all the same functionality and applications, just moved around a little bit to prevent me from getting sucked into my phone.</p>
<p>The goal of previous iterations has always been the "magic button:" Can I set up my device so that I touch it once and then put it down? Because of iOS17's new features… I can achieve 80% of that without even unlocking my phone.</p>
<h2 id="lockscreen">Lockscreen</h2>
<p>The lockscreen now contains the layer of my device intended to "quickly capture" a thought, task, or event. Ideally I do one thing on this layer, then bounce back into the real world without being sucked in.</p>
<figure class="thirds">
<img src="/images/articles/2024/01/lockscreen-empty.png" alt="lockscreen">
<img src="/images/articles/2024/01/lockscreen-notification.png" alt="lockscreen-notification">
<img src="/images/articles/2024/01/lockscreen-launcher.png" alt="lockscreen-launcher">
<figcaption>
<p>On top: Fantastical shows the upcoming calendar item.</p>
<p>Below the clock: four "complications" left to right:</p>
<ol>
<li>Launch <a href="https://overcast.fm/">Overcast</a> (my podcast app)</li>
<li>Start Dictation to <a href="https://evantravers.com/articles/tags/draftsapp">Drafts</a></li>
<li>Start Writing to <a href="https://evantravers.com/articles/tags/draftsapp">Drafts</a></li>
<li>Launch <a href="https://evantravers.com/articles/series/hammerspoon-headspace/">Headspaces</a></li>
</ol>
</figcaption>
</figure>
<p>Interactive widgets are a game-changer. I can pick up my phone, throw something into Drafts, and put it down without even seeing all the notifications, applications, or any other distractions.</p>
<p>The Headspace Launcher is the <em>same</em> shortcut I've written about <a href="https://evantravers.com/articles/2022/01/12/ios-homescreen-2022/#shortcuts-launchers">using shortcuts and headspaces</a> in a new location.</p>
<p>I have noticed that (with my iPhone 13 Mini) there seems to be some kind of time out that can keep a longer running lockscreen shortcut from finishing. I don't have a clear answer, but I have noticed that if the phone is unlocked, there is no timeout.<sup id="fnref1"><a href="#fn1">1</a></sup></p>
<p>Therefore it's pretty common for me to pick up my phone, faceID unlock, I hit the launcher and then immediately swipe up to "unlock" the phone, but I'm really still seeing the Launcher menu and executing one thing.</p>
<p>It's not perfect, but it works. 🤷</p>
<p>As it is, 95% of the time I pick up my phone, I touch one icon on the lockscreen and never reach the next layer: the homescreen.</p>
<h2 id="homescreen">Homescreen</h2>
<p>My homescreen is the simplest it's ever been.</p>
<figure class="right">
<img src="/images/articles/2024/01/homescreen.png" alt="homescreen">
<figcaption>
<p>Using the giant Things widget and being able to check off items from the Homescreen has helped me stay on task.</p>
</figcaption>
</figure>
<p>It contains only the three applications that hold the deeper database of my systems: Fantastical for time, Things for tasks, Obsidian for thoughts.</p>
<p>This layer answers the second reason I pick up a mobile device: querying into the systems that run my life. Because the previous layer of the lockscreen answers 99% of all "quick actions" I pretty much only unlock my phone to do that deeper query, and I rarely drift into deeper querying when my original intent was "quick-add".</p>
<p>It's been <em>great</em>.</p>
<p>Removing Messages and Phone from my Dock was a weird change. However my friend group has slowly split up along the Messages/Signal/Telegram battlefronts, so I was frequently deciding where to message someone. As it is I typically start conversations using Siri anyway.</p>
<p>Because my notifications are so restricted, my Notification Center is messaging anyway, so that provides a way to re-enter conversations.</p>
<p>Obsidian continues to be more useful for me… I use it as my digital bible now that I pay for Obsidian Sync and it loads faster than most other Bible apps.</p>
<h2 id="widgets">Widgets</h2>
<p>The only thing left is the widget panel, which I use for a few things that don't quite fit, but are nice to have. Kind of a utilities folder of specific logging tools for on the go.</p>
<figure class="dual">
<img src="/images/articles/2024/01/widgets.png" alt="widgets">
<img src="/images/articles/2024/01/widgets-weight.png" alt="widgets-weight">
<figcaption>
<p>From top to bottom, left to right:</p>
<ol>
<li><a href="https://apps.apple.com/us/app/locket-widget/id1600525061">Locket</a> is photos of my loved ones.</li>
<li>Shortcut for recording my weight in the morning.</li>
<li><a href="https://chartyios.app/">Charty</a> Widget that shows rolling average of weight.</li>
<li><a href="https://www.foodnoms.com/">Foodnoms</a> for recording food/water</li>
<li><a href="https://www.ynab.com/">YNAB</a> for recording transactions</li>
<li>Battery Widget</li>
</ol>
</figcaption>
</figure>
<p>The Weight Shortcut and Chart are an updated version of one <a href="https://evantravers.com/articles/2020/11/07/day-of-automation/">I wrote up in 2020</a>. Update Weight now calls the shortcut that builds the Chart and updates the widget, so this screen has a pretty powerful accountability tool without it being in my face all the time.</p>
<h2 id="part-b8a856d">…</h2>
<p>I am happy with this setup and have been using it since the update in September.</p>
<p>The "layers" concept has been helpful for mental organization and to keep me from floating away into the infinite expanse of digital distraction<sup id="fnref2"><a href="#fn2">2</a></sup>. The Headspaces being exactly the same system on desktop and mobile allows me have the same time-tracking tools everywhere.</p>
<p>As usual… don't copy 100% my setup. Take some principles and try them on for size. If you do nothing else, auditing and harshly restricting your notifications so that you are only buzzed for something truly important is the best thing you can do for your focus.</p>
<h2 id="prior-art">Prior Art</h2>
<ul>
<li><a href="https://evantravers.com/articles/2019/06/03/minimal-ios-homescreen-2019/">2019</a></li>
<li><a href="https://evantravers.com/articles/2022/01/12/ios-homescreen-2022/">2022</a></li>
</ul>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p><a href="https://joschua.io/">Joschua</a> linked me to <a href="https://apple.stackexchange.com/questions/147453/how-can-i-extend-the-lock-screen-timeout-in-ios/367547#367547">this stackoverflow post</a> on the same topic. Seems there's some places on the screen you can keep touching to keep the screen alive too. <a href="#fnref1">↩</a></p>
</li>
<li id="fn2">
<p>I'm also using <a href="https://apps.apple.com/us/app/refocus-screen-time-blocker/id1645639057">Refocus.app</a> to create an additional layer of friction for websites that distract me. <a href="#fnref2">↩</a></p>
</li>
</ol>
</div>
Setting Up Nix Dev Environment for Middleman and Rubyhttps://evantravers.com/articles/2024/01/04/setting-up-nix-dev-environment-for-middleman-and-ruby/2024-01-04T13:42:00+00:002024-01-04T19:27:08+00:00Evan Travers<p>Wanna know why I've stopped working on <a href="https://evantravers.com/articles/2022/07/26/an-ode-to-advent-of-code/">Advent of Code</a> on <a href="https://github.com/evantravers/adventofcode/blob/master/2023/elixir/lib/08-haunted-wasteland.ex">Day 8</a>? This problem right here! 🤦</p>
<p>So when I initially <a href="https://evantravers.com/articles/2023/11/28/moving-my-dotfiles-to-nix/">switched to Nix for dotfiles</a> and setup NixOS I was able to set up the blog, but I wasn't reaaaaally using Nix. I used Nix to set up my trusty...</p><p>Wanna know why I've stopped working on <a href="https://evantravers.com/articles/2022/07/26/an-ode-to-advent-of-code/">Advent of Code</a> on <a href="https://github.com/evantravers/adventofcode/blob/master/2023/elixir/lib/08-haunted-wasteland.ex">Day 8</a>? This problem right here! 🤦</p>
<p>So when I initially <a href="https://evantravers.com/articles/2023/11/28/moving-my-dotfiles-to-nix/">switched to Nix for dotfiles</a> and setup NixOS I was able to set up the blog, but I wasn't reaaaaally using Nix. I used Nix to set up my trusty <a href="https://asdf-vm.com/"><code>asdf-vm</code></a> and used that to handle ruby and bundler to handle gem depedencies.</p>
<p>While working on more complicated projects I discovered this approach doesn't work well. It really hangs up if the ruby gem wants to build a native extension… which a <em>lot</em> of things in rails does.<sup id="fnref1"><a href="#fn1">1</a></sup></p>
<p>The More Correct Answer™️ is to let Nix handle all dependencies, including gems and NPM packages.</p>
<p>I initially went down the path of looking at <a href="https://devenv.sh/">cachix/devenv</a> and then <a href="https://www.jetpack.io/devbox/">jetpack-it/devbox</a>. Both are very nice, but I couldn't make much headway, I suspect because I didn't really understand enough about Nix yet.<sup id="fnref2"><a href="#fn2">2</a></sup></p>
<p>As it was, I fought for weeks chasing different Nix errors. Sometimes I'd get it working on one device, but not another. My "lab journal" note in drafts and commit logs are filled with experiments, guesses and frustrations.</p>
<p>Here's where I landed:</p>
<p>I stopped using either devenv or devbox and wrote a simple flake based on the one from the documentation.</p>
<div class="highlight"><pre class="highlight nix"><code><span class="p">{</span>
<span class="nv">description</span> <span class="o">=</span> <span class="s2">"evantravers.com (middleman, ruby, node)"</span><span class="p">;</span>
<span class="nv">inputs</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">nixpkgs</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"nixpkgs/nixpkgs-unstable"</span><span class="p">;</span>
<span class="nv">flake-utils</span><span class="o">.</span><span class="nv">url</span> <span class="o">=</span> <span class="s2">"github:numtide/flake-utils"</span><span class="p">;</span>
<span class="p">};</span>
<span class="nv">outputs</span> <span class="o">=</span> <span class="p">{</span> <span class="nv">self</span><span class="p">,</span> <span class="nv">nixpkgs</span><span class="p">,</span> <span class="nv">flake-utils</span> <span class="p">}:</span>
<span class="nv">flake-utils</span><span class="o">.</span><span class="nv">lib</span><span class="o">.</span><span class="nv">eachDefaultSystem</span> <span class="p">(</span><span class="nv">system</span><span class="p">:</span>
<span class="kd">let</span>
<span class="nv">pkgs</span> <span class="o">=</span> <span class="nv">nixpkgs</span><span class="o">.</span><span class="nv">legacyPackages</span><span class="o">.</span><span class="p">${</span><span class="nv">system</span><span class="p">};</span>
<span class="nv">rubyEnv</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">bundlerEnv</span> <span class="p">{</span>
<span class="c"># The full app environment with dependencies</span>
<span class="nv">name</span> <span class="o">=</span> <span class="s2">"middleman-env"</span><span class="p">;</span>
<span class="kn">inherit</span> <span class="p">(</span><span class="nv">pkgs</span><span class="p">)</span> <span class="nv">ruby</span><span class="p">;</span>
<span class="nv">gemdir</span> <span class="o">=</span> <span class="sx">./.</span><span class="p">;</span> <span class="c"># Points to Gemfile.lock and gemset.nix</span>
<span class="p">};</span>
<span class="nv">buildPackages</span> <span class="o">=</span> <span class="p">[</span>
<span class="nv">pkgs</span><span class="o">.</span><span class="nv">bundix</span>
<span class="nv">rubyEnv</span>
<span class="nv">rubyEnv</span><span class="o">.</span><span class="nv">wrappedRuby</span>
<span class="nv">pkgs</span><span class="o">.</span><span class="nv">nodejs</span>
<span class="p">];</span>
<span class="kn">in</span> <span class="kn">with</span> <span class="nv">pkgs</span><span class="p">;</span>
<span class="p">{</span>
<span class="nv">devShells</span><span class="o">.</span><span class="nv">default</span> <span class="o">=</span>
<span class="nv">mkShell</span> <span class="p">{</span>
<span class="nv">env</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"NODE_OPTIONS"</span> <span class="o">=</span> <span class="s2">"--openssl-legacy-provider"</span><span class="p">;</span>
<span class="p">};</span>
<span class="nv">buildInputs</span> <span class="o">=</span> <span class="nv">buildPackages</span> <span class="o">++</span> <span class="p">[</span>
<span class="nv">pkgs</span><span class="o">.</span><span class="nv">solargraph</span>
<span class="p">];</span>
<span class="p">};</span>
<span class="nv">packages</span><span class="o">.</span><span class="nv">default</span> <span class="o">=</span>
<span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="p">{</span>
<span class="nv">name</span> <span class="o">=</span> <span class="s2">"build the website"</span><span class="p">;</span>
<span class="nv">src</span> <span class="o">=</span> <span class="sx">./.</span><span class="p">;</span>
<span class="nv">buildInputs</span> <span class="o">=</span> <span class="nv">buildPackages</span><span class="p">;</span>
<span class="nv">buildPhase</span> <span class="o">=</span> <span class="s2">"</span><span class="si">${</span><span class="nv">rubyEnv</span><span class="si">}</span><span class="s2">/bin/middleman build"</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>The way to get ruby dependencies into nix is as follows:</p>
<ol>
<li>Drop into a nix shell that has a few packages: <code>nix-shell -p bundix ruby</code>.</li>
<li>Now we have access to bundler, so run <code>bundle lock</code> to generate a Gemfile.lock.</li>
<li>That gemfile.lock describes the location and identity of dependencies: <code>bundix -l</code> translates that into a nix expression in gemset.nix.</li>
<li>In the flake you need to import a ruby with that gemset.nix expression as part of the environment (the <code>bundlerEnv</code> above.)</li>
</ol>
<p>Seems straightforward right? Well…</p>
<p>My flake can't be used to create a shell with the dependencies needed to debug until it compiles cleanly, so you need to keep using <code>nix-shell</code>. When it works, it <strong>works</strong>. But until then…</p>
<p>So what was going wrong for so long? Some of my mistakes:</p>
<h2 id="force-ruby-platform">Force ruby platform</h2>
<p>For a long time I was wrestling on and off with various ruby gems that were building native extensions or had platform specific versions.</p>
<p>I didn't solve this problem, I just sidestepped it.</p>
<p>I ran <code>bundle config set --local force_ruby_platform true</code>, re-ran <code>bundle lock</code> and removed any references to individual platforms. For this simple blog, that worked fine. It won't work for a more complicated situation, so I'll learn more about that later.</p>
<h2 id="you-have-to-include-the-gemset-nix">You have to include the gemset.nix</h2>
<p>For a long time I was generating the gemset.nix with bundix, but then not including it in the flake. 🤦 After I had used the <code>let</code> portion to define the <code>bundlerEnv</code>, I needed to include it in the <code>buildPackages</code> portion of the <code>mkShell</code>. I was defining all the gems… but not using them.</p>
<h2 id="stop-using-bundler">Stop using bundler</h2>
<p>I kept running <code>bundle exec</code> like an idiot.</p>
<p>For <em>years</em> I have dodged <em>so many</em> ruby bullets by simply running <code>bundle exec</code> in front of every executable call and it's become muscle memory. By doing this I ensure that the local bundle calls the gems I specified in this project's gemfile, not some global copy floating somewhere else.</p>
<p>Except here… I don't want to use the local gems I pulled with <code>bundle install</code>… in fact I don't even want to install them using bundler. All bundler needs to do is describe the dependency graph to be translated by bundix into a nix expression. That's it. </p>
<p>For a bit <code>bundle exec middleman build</code> was indeed working… because the gems installed into ./vendor were sufficient to run that code. As I dug into some weird issues with <code>bundle exec middleman server</code> I saw that the stack trace included gems in ./vendor/cache <strong>and</strong> in /nix/store… whoopsie.</p>
<h2 id="just-match-the-mismatched-sha256">Just match the mismatched sha256</h2>
<p>For a long time I was fighting an error in my flake where it was getting a sha different than what it expected. I don't know why it happened: it might be that <a href="https://github.com/rubygems/rubygems/pull/7324">my particular version of bundler wasn't handling platform-specific gems correctly</a>, or that <a href="https://github.com/nix-community/bundix/issues/109">bundix wasn't interpreting the file correctly</a>.</p>
<p>Complicated causes don't require complicated solutions. After reading <a href="https://technogothic.net/pages/JekyllOnNix/">technogothic's post</a>, I took the SHA that nix said it was expecting and put it into the gemset.nix. Problem solved.</p>
<h2 id="you-cant-write-to-read-only">You can't write to read-only</h2>
<p>After all this… I could run <code>middleman server</code> and everything started! I happily opened localhost:4567 in my browser aaaaand nothing. If I interrupted the server I got a cryptic stacktrace about files, basenames, recursion… and it was slightly different every time?</p>
<p>I kept googling traces, messing with things, and remembered I'd seen this before. I ran <code>middleman build</code> and it worked <em>just fine</em>.</p>
<p>Do I learn from my mistakes? Slowly. This is the same problem that I faced when <a href="https://evantravers.com/articles/2023/11/28/moving-my-dotfiles-to-nix/#treesitter-wants-to-compile">trying to use Lazy.nvim</a>. Nix was mounting my website's files and dependencies in the immutable nix store, and something in middleman's livereload server really hates that.</p>
<p>For now, I can run <code>middleman server --watcher-disable</code> and move on with my life.</p>
<h2 id="part-b8a856d">…</h2>
<p>There's been a string of long-running late-night texts between me and friends over the past month as I fought to bring my blog into Nix.</p>
<div class='admonition chat_out'><div class='admonition__title'>Me</div><p>(you probably know all this crap… but it feels like a breakthru to me)</p>
</div>
<div class='admonition chat_in'><div class='admonition__title'>Friend</div><p>yea</p>
</div>
<div class='admonition chat_in'><p>You've never dated a crazy girl.. but that's what it feels like</p>
</div>
<div class='admonition chat_in'><p>the highs are high and the lows are low</p>
</div>
<div class='admonition chat_out'><p>🤔</p>
</div>
<p>He's not <em>totally</em> wrong. Another friend, @qmx, warned me to stay away from Nix… at the moment I'm persevering through the pain. At the moment I haven't found a solution to the middleman live-reload watcher working again…<sup id="fnref3"><a href="#fn3">3</a></sup> but if I freeze the flake.lock I can guarantee that my blog will at least run for a long time even though middleman's future is in question, and that's a good feeling.<sup id="fnref4"><a href="#fn4">4</a></sup></p>
<p>I'm going to try to stand up a more complicated rails app with databases, caches, and a separate microservice… we'll see how I feel about it after that.</p>
<p>If all else fails, I can fall back to letting nix stand up services, define <code>asdf-vm</code>, and keep rolling like I always have, but I want the reproducibility… and longevity that nix promises. I started this on a WSL NixOS, worked on it on an old intel Mac, and am finishing the spellchecking on a M1 ARM Mac... and for each platform all I did was <code>cd</code> into the folder, <code>direnv</code> triggered the nix evaluation, and I could start work.</p>
<p>It's a bright promise, we'll see if it holds up to be a real future.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://github.com/nix-community/bundix">bundix reference</a></li>
<li><a href="https://nixos.org/manual/nixpkgs/stable/#developing-with-ruby">nixos wiki on ruby</a></li>
<li><a href="https://discourse.nixos.org/t/cant-use-ruby-sassc-gem-fails-at-runtime-with-ffi/4023">some discussion of gems and native extensions</a></li>
<li><a href="https://unix.stackexchange.com/questions/440569/ruby-on-nixos-ffi-gem-installation-failing-when-building-native-extensions">more stackexchange on native extensions</a></li>
</ul>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>This was the same problem that I encountered using Lazy.nvim to try and install LSP grammars. Nix is evaluating everything in its read only store… so temp build files break its functional perfection. <a href="#fnref1">↩</a></p>
</li>
<li id="fn2">
<p>If I go back to one, it'll probably be devenv. Devbox's decision to abstract away nix expressions and only use json means I'm not sure how to use Bundix with it. I guess I'd have to write a custom package and use that as an input in the Devbox's json? Unsure. Devbox is available as a package in home-manager, devenv has nice caching… problem for the next project that requires services and databases. <a href="#fnref2">↩</a></p>
</li>
<li id="fn3">
<p>I could probably use direnv to reload the server every time change a file, but that feels heavy. I just need to dive into <code>middleman server</code>'s documentation… I could probably have it write a cache folder I control. <a href="#fnref3">↩</a></p>
</li>
<li id="fn4">
<p>Gives me the confidence to extend <a href="https://evantravers.com/articles/2022/06/17/extending-redcarpet-for-admonition-blocks/">my custom admonition markup</a> to ape <a href="https://xeiaso.net/">Xe's cute conversations</a>. <a href="#fnref4">↩</a></p>
</li>
</ol>
</div>
2023https://evantravers.com/articles/2024/01/01/2023/2024-01-01T15:00:00+00:002024-01-01T22:02:23+00:00Evan Travers<p>This year's theme was set as <a href="https://evantravers.com/articles/2022/12/31/review-2022/#2023"><em>Wake Up</em></a>, intending to take action, not sleep on things. It was a good guide to begin the year, and providentially took on new meanings as the Lord revealed new things for me to work on and submit myself to Him.</p>
<p>I'd say...</p><p>This year's theme was set as <a href="https://evantravers.com/articles/2022/12/31/review-2022/#2023"><em>Wake Up</em></a>, intending to take action, not sleep on things. It was a good guide to begin the year, and providentially took on new meanings as the Lord revealed new things for me to work on and submit myself to Him.</p>
<p>I'd say that this was a very hard year emotionally. Just a lot going on in the world<sup id="fnref1"><a href="#fn1">1</a></sup> and in my heart. At this moment I am grateful and easily resting in God's provision and faithfulness… but that has been harder at different times in the year.</p>
<h2 id="creating">Creating</h2>
<p>I think this year I wrote 53 articles here, 13 more than last year. Some of my favorites:</p>
<ul>
<li><a href="https://evantravers.com/articles/2023/02/16/raycast-review-as-an-longtime-alfred-user/">Raycast Review as an Longtime Alfred User</a></li>
<li><a href="https://evantravers.com/articles/2023/08/01/sketchnoting-sermons/">Sketchnoting Sermons</a></li>
<li><a href="https://evantravers.com/articles/2023/04/06/magsafe-tenting-and-wearable-keyboards/">MagSafe Tenting and Wearable Keyboards</a></li>
<li><a href="https://evantravers.com/articles/2023/06/26/he-who-grasps-the-heel/">He Who Grasps the Heel</a></li>
<li><a href="https://evantravers.com/articles/2023/05/21/and-there-arose-a-generation-who-knew-not/">And There Arose a Generation Who Knew Not</a></li>
</ul>
<p>I am still making a lot of unlisted videos just for friends, although now I'm using OBS and Youtube because I'm too poor to pay for Loom at the moment.</p>
<p>I touched 2303 notes in Obsidian this year. That's almost double the count from last year. There's a lot of thoughts brewing in there… some shallow research notes about camping and hobbies, but also an essay about burnout and What I Think About This AI Thing.</p>
<p>I'm going to start a new habit this year (Lord willing!) to write more. I've had some essays I really care about jammed in my gullet for months. I need to spend time on them. The two changes: a glance at my writing <a href="https://evantravers.com/articles/2022/08/30/writing-in-obsidian/">Kanban board</a> as part of Weekly Review, and morning prompt to write for just five minutes.</p>
<p>I <em>really</em> need to get off Middleman. It's been really stymieing me to do some new things I want to do, and it's pinned on Ruby 2.7.6 which is EOL.</p>
<h3 id="reading">Reading</h3>
<p>I started 20 books this year, finishing 19. 12 fiction, 8 non-fiction. Not bad, but my reading is still kind of "in between" things, not a set aside activity. I really would benefit from having a working hours reading habit. I may try to implement that in a later quarter. I <a href="https://evantravers.com/articles/2023/07/18/recently-reading/">wrote about some highlights here</a>.</p>
<h3 id="work">Work</h3>
<p>Work has been blessedly flexible during some difficult personal seasons. On a day to day basis it's sometimes hard to see the progress we make, but I have been able to cross off some years-long product goals this year. Always keep a list of things you want changed, and revisit it every once in a while. If someone fixes that thing… show them and thank them for making your wishes come true!</p>
<p>I think that this next year will hold the opportunity to take user experience to the next level of maturity at the day job. I'm excited about what that means both for my personal growth as well as our organization's ability to serve and consider the needs of our users.</p>
<p>I do really love my team, wonderful folks with some new faces and an incredible mix of skills. I'm very honored to work with such fine folks and look forward to seeing what 2024 holds.</p>
<h2 id="spiritual-life">Spiritual Life</h2>
<p>I find myself rushing to the Lord for help a lot these days. I need him to not be frustrated at my kids. I need his strength to help me work as unto the Lord.</p>
<p>Being a part of a dedicated and humble small group has really helped this year. Three of you have 1:1 coffee'd with me monthly, praying with me and for me as I walked through this year. (Thank you!!)</p>
<p>I've made three habit-based adjustments to my quiet time that has helped… I track prayer requests (more on that later), I've been <a href="https://evantravers.com/articles/2023/08/01/sketchnoting-sermons/">sketchnoting sermons and sharing the notes with my kids</a> and I've also had "Study" spread in my Bullet Journal that contains all the notes from Quiet Time, Sunday School, and Small Group. Having the thoughts from all those spiritual conversations open in front of me in the morning sparks some thoughts and connections and helps me detach from the moment and attach to the conversation that God is having with me through his word and his people.</p>
<p>I'd say that the Lord is teaching me to rely upon him more this year than any before, and while I'm white-knuckling that reliance more than I'd like to admit, it <em>is</em> sweet to trust in Jesus.</p>
<h2 id="health">Health</h2>
<p>This is an area that really needs attention, and my <a href="https://evantravers.com/articles/2023/03/28/obsidian-12wy-review-template/">12WY</a> goal for Q1 is starting here:</p>
<p>I've had <em>really</em> bad sleep for months. Taking magnesium and trying to see the sun rise each morning has helped the past few weeks, and I'm going to continue this. It's also been sweet time of quiet prayer and reflection to start the day. Tomorrow morning I'll be wrapped in a blanket, sitting on a camping pad in my backyard, getting my morning photonic reset.</p>
<p>I am about 20lb heavier than I should be. I've had consistent workouts, but not determined. I also stress eat and "reward" myself with sugar. I've also had some lingering injuries to my right knee and left foot that I need to get addressed.</p>
<h2 id="relationships">Relationships</h2>
<p>I often say it to her, but it's worth saying to the world: I don't deserve my wife. She's so kind and strong. I have come to appreciate her more and more this year as the kids grow in their ability to delight and distract. She's an amazing woman. I am so grateful to God for her.</p>
<p>This year we've had a lot more little weekend adventures. Many of them originate with my thoughtful wife, but I'm trying to "join the party planning committee" and bring some creative energy to our afternoons and weekends.</p>
<p>I have to give special credit to my friend Julie for the inspiration to take a PTO day for every birthday in my home. Many of the sweetest memories and meals from our year occur on those three-day weekends.</p>
<p>I want to carry forward this pattern next year… more small restful adventures create pockets of rest and togetherness throughout the year… as well as spreading the odds that our big plans get canceled by toddler-borne illness.</p>
<p>This year I've continued to measure and celebrate in my weekly/monthly summaries activities that I want to promote:</p>
<ul>
<li>meaningful and life-giving 1:1 conversations (44 of your this year, thank you!)</li>
<li>memorable meals (8 off the top of my head)</li>
<li>family outings (31 I could count)</li>
<li>serving others through hospitality (I counted 20+ families this year Sarah for whom Sarah created meals!)</li>
<li>prayer requests answered (I just started tracking this again, but I was able to find 20ish answers to prayer in my cursory reading of my journals.)</li>
</ul>
<p>Also special shout-out to those of you who text, discord, or coffee regularly with me. Thank you for your encouragement. It means the world to me.</p>
<h2 id="2024">2024</h2>
<p>The theme that seems to resonate at the moment was providentially provided by my new friend Greg: <em>Own Up</em>. 2023 allowed me to <em>Wake Up</em> to some of the problems and realities of my life… now it's time to arise tomorrow and own up to that reality, trusting in the Lord for the result and moving in his strength.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>I remain unsubscribed (and blocked!) from most news sources… but there's been lot of historic things happening this year. <a href="#fnref1">↩</a></p>
</li>
</ol>
</div>
Moving My Dotfiles to Nixhttps://evantravers.com/articles/2023/11/28/moving-my-dotfiles-to-nix/2023-11-28T16:15:00+00:002023-11-28T17:41:49+00:00Evan Travers<p><img alt='' src='https://evantravers.com/images/articles/2023/11/wsl.png'></p><p>I've been experimenting with <a href="https://nixos.org/">Nix</a>. I have a PC that is <em>way</em> more powerful than my older Macbook. While experimenting with Ubuntu on WSL, and porting <a href="https://github.com/evantravers/dotfiles/tree/before-nix">my existing dotfiles</a> over, I remembered my friend Tom talking about Nix and NixOS and decided to give...</p><p><img alt='' src='https://evantravers.com/images/articles/2023/11/wsl.png'></p><p>I've been experimenting with <a href="https://nixos.org/">Nix</a>. I have a PC that is <em>way</em> more powerful than my older Macbook. While experimenting with Ubuntu on WSL, and porting <a href="https://github.com/evantravers/dotfiles/tree/before-nix">my existing dotfiles</a> over, I remembered my friend Tom talking about Nix and NixOS and decided to give it a whirl.</p>
<p>Two weeks later this article is being written on NixOS, in WSL, using my full dev environment. 🎉</p>
<h2 id="nix">Nix</h2>
<p>The orange site and red site are filled with <a href="https://shopify.engineering/what-is-nix">excellent articles about Nix</a> and what it can do. For the sake of this article a brief overview.</p>
<p>The word Nix points to three things:</p>
<ol>
<li>A functional language<sup id="fnref1"><a href="#fn1">1</a></sup> for describing configurations of software.</li>
<li>A package manager using the Nix Language to handle installation and path management of software configurations.</li>
<li>A *nix OS (NixOS) that can be configured using the Nix Language.</li>
</ol>
<p>These three things provide a couple features very desirable to me:</p>
<ul>
<li>Using a strict configuration for my dev environment means that I can sync my setup between my work laptop, my personal laptop, and now this development WSL PC.</li>
<li>NixOS being a configuration-first OS means I can summon a direct copy of it without having to spend hours setting it up. This is advantageous for virtualisation or if I want to move my WSL setup to a dual-boot someday.</li>
</ul>
<h2 id="macos-or-wsl">MacOS or WSL</h2>
<p><a href="https://wezfurlong.org/wezterm/">Wezterm</a> works on both my "host" OS. I just run the executable and put my wezterm.lua wherever it needs to be.</p>
<p>I do have <a href="https://github.com/evantravers/dotfiles/blob/010f1ab583427019b00d4a145dfc3aa435c3df3d/home.nix#L41-L46">a wild little script</a> that runs and installs the wezterm terminfo file. <em>Very</em> un-nix.</p>
<p>On my Macs I can't run NixOS but I can at least <a href="https://nixos.org/manual/nix/stable/installation/installation.html">install the Nix Package manager</a>. There's a relatively complicated install process involving a separate volume and mounting… but the installer is pretty easy to run.</p>
<p>On the Windows PC I used the <a href="https://github.com/nix-community/NixOS-WSL">WSL2 instructions</a> provided by NixOS. I also added</p>
<div class="highlight"><pre class="highlight lua"><code><span class="n">default_domain</span> <span class="o">=</span> <span class="s1">'WSL:NixOS'</span>
</code></pre></div>
<p>to my Windows <code>.wezterm.lua</code> so that Wezterm will connect to WSL on launch.</p>
<h2 id="home-manager">Home Manager</h2>
<p>After NixOS or Nix Package Manager have been installed we can install <a href="https://nix-community.github.io/home-manager/">Home Manager</a> manage our dev environment.</p>
<p>It's specifically built for this problem and replaces what I was doing with my <code>Brewfile</code>, <code>Makefile</code>, and <code>stow</code>.</p>
<p>I took it slowly over a couple of weeks. At first my home-manager consisted of <a href="https://github.com/evantravers/dotfiles/blob/a12d053cb440a0e73859c8011ba60958f0bec112/home.nix">installing some packages and moving my existing dotfiles to the right place</a>. Very un-Nix-like but it totally worked.</p>
<p>As I explored the options appendix and debugged problems I wound up <a href="https://github.com/evantravers/dotfiles/blob/010f1ab583427019b00d4a145dfc3aa435c3df3d/home.nix">here today</a>. I chose not to put <em>everything</em> into the Nix configuration and keep some of the configuration in files for backwards compatibility. In a pinch you could <code>stow</code> or <code>ln</code> those folders/files in your home just like I was before.</p>
<p>Let's explore some of the bumps in the road.</p>
<h2 id="neovim">Neovim</h2>
<p>I eventually moved to using the <code>programs.neovim</code> configuration provided by Home Manager. I really wrestled with getting everything setup correctly. I think part of the problem was existing executables and configuration files still in path on OSX that was confusing the universe but in the end there were two big problems:</p>
<h3 id="treesitter-wants-to-compile">Treesitter wants to compile</h3>
<p>Treesitter is awesome. But it wants to run scripts and compile things. Nix's store doesn't love that… it wants each folder to be idempotent and sets them to Read Only.</p>
<p>I added <a href="https://github.com/evantravers/dotfiles/blob/010f1ab583427019b00d4a145dfc3aa435c3df3d/home.nix#L135-L145">pkgs.vimPlugins.nvim-treesitter</a> and eventually found the sequence of settings to make it work:</p>
<ul>
<li>add <code>config</code> block to configure highlight and indent on, but I got errors… set <code>type = "lua";</code>.</li>
<li>use <code>withAllGrammars</code> so that it automatically gets all of them.</li>
<li>everything is installed but not loading, use <code>packadd</code> to force it to load the package.</li>
</ul>
<p>All this wasn't working and I realized that…</p>
<h3 id="lazy-nvim-takes-over-rtp">Lazy.nvim takes over RTP</h3>
<p>Turns out my beloved <a href="https://github.com/folke/lazy.nvim">Lazy.nvim</a> completely takes over the <a href="https://vimhelp.org/options.txt.html#%27runtimepath%27">RTP</a>. This means that plugins loaded manually (or by Nix!) can't be found by neovim. Someone else <a href="https://discourse.nixos.org/t/neovim-cant-load-plugins/31189/6">pointed out there was a setting</a>, so <a href="https://github.com/evantravers/dotfiles/commit/6bd0928664fa4e0eb75ec18f92424256e1d6a723">I was able to disable this</a> and suddenly neovim could see plugins loaded by Nix.</p>
<p>These may be bad and wrong, if so… shoot me an email?</p>
<h2 id="tmux">Tmux</h2>
<p>Having learned from my Neovim mistakes Tmux was a bit easier. Like Treesitter, <a href="https://github.com/tmux-plugins/tpm">TPM</a> wants to modify it's read-only folders. I moved some settings into the configuration, install the plugins using Home Manager, and use my existing config for theming and style.</p>
<h2 id="fish">Fish</h2>
<p>I've been using a stripped down Fish install for a while now. I almost wonder if I should use it at all. I basically just install it and use Home Manager to enable Starship.</p>
<p>I still am using <code>asdf</code> in some places. It'll likely phase out as I get better at flakes.</p>
<h2 id="part-b8a856d">…</h2>
<p>I'm starting to play with <a href="https://devenv.sh/">devenv.sh</a> and flakes to create my development environments. At the moment I'm still making heavy use of <code>asdf</code> to handle ruby/js package management. I am wrestling with how to manage gems that want to build native extensions. I've been warned by kind friends that it's enough to drive one crazy… but I've got the bandwidth presently for the challenge.</p>
<p>I like the idea of only enabling LSP executables and maybe even entire Neovim configurations per project using flakes and overlays. I've got a really simplistic version of this working using <code>direnv</code> at the moment.</p>
<p>All this <em>very much</em> falls under <a href="https://xkcd.com/1319/">XKCD's warning of automation addiction</a>. For me it's worthwhile to learn a little bit about Nix and to face the puzzle of *nix and pathing problems.</p>
<p>I really love that I can run <code>home-manager switch</code> and know that everything I need to start working gets automatically put in the right place. That's pretty hard to beat.</p>
<p>Now I suppose it's actually time to read Nix's actual documentation. :P</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>Nix <em>loves</em> the trailing <code>;</code>. Absolutely required. Really hard for my ruby/JS brain. <a href="#fnref1">↩</a></p>
</li>
</ol>
</div>
Rituals Circa 2023https://evantravers.com/articles/2023/11/13/rituals-circa-2023/2023-11-13T23:59:00+00:002023-11-13T18:32:17+00:00Evan Travers<p><img alt='' src='https://evantravers.com/images/articles/2023/11/review.jpeg'></p><p>I have a <strong>lot</strong> of checklists and systems. Definitely too much. My friend Errin asked if he could steal the templates, so some quick thoughts.</p>
<p>Rituals help me by…</p>
<ul>
<li>Providing a sense of progression and time-pass when working remote.</li>
<li>Holding long-term...</li>
</ul><p><img alt='' src='https://evantravers.com/images/articles/2023/11/review.jpeg'></p><p>I have a <strong>lot</strong> of checklists and systems. Definitely too much. My friend Errin asked if he could steal the templates, so some quick thoughts.</p>
<p>Rituals help me by…</p>
<ul>
<li>Providing a sense of progression and time-pass when working remote.</li>
<li>Holding long-term goals in front of my daily thoughts.</li>
<li>Correctly balancing lead and lag measures.<sup id="fnref1"><a href="#fn1">1</a></sup></li>
<li>Highlighting Weak Signals<sup id="fnref2"><a href="#fn2">2</a></sup> before problems escalate with health or relationships.</li>
</ul>
<p>I am a crazy man. I have built this up over <em>years</em>. Don't copy this verbatim. Steal one thing that's good, but be inspired but terrified by the rest. </p>
<h2 id="daily-journal-daily-yyyy-mm-dd-md">🗒️ Daily: <code>journal/daily/YYYY-MM-DD.md</code></h2>
<div class="highlight"><pre class="highlight markdown"><code><span class="nn">---</span>
<span class="na">follower</span><span class="pi">:</span>
<span class="na">husband</span><span class="pi">:</span>
<span class="na">father</span><span class="pi">:</span>
<span class="na">health</span><span class="pi">:</span>
<span class="na">create</span><span class="pi">:</span>
<span class="na">work</span><span class="pi">:</span>
<span class="na">plan</span><span class="pi">:</span>
<span class="nn">---</span>
</code></pre></div>
<p>This is the template for the daily note. I rate myself on my intentions from 1-3 in areas of my life:
- 1 = Didn't try.
- 2 = Showed up.
- 3 = Broke a sweat.</p>
<p>The following becomes a sort of "<a href="https://evantravers.com/articles/tags/bullet-journal/">bullet journal</a>" for what happened that day. Highlights, funny things my kids said, insights from meetings at work… etc.</p>
<h3 id="daily-plan-workdays-9a">🌅 Daily Plan (Workdays @ 9a)</h3>
<div class="highlight"><pre class="highlight markdown"><code><span class="gh"># 🧩 Daily Plan</span>
In order to be effective with my day, I design my day with the right balance of focus, time, and effort.
<span class="p">
-</span> [ ] 🙏 Pray. Wisdom to know, strength to do.
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] ⭐ Protect the [essential</span><span class="p">](</span><span class="sx">things:///show?id=today&filter=@Meazure%20Learning,$High</span><span class="p">)</span>.
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 👨👩👧👦 Prioritize [family projects</span><span class="p">](</span><span class="sx">things:///show?id=anytime&filter=@Family</span><span class="p">)</span>.
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 👨 Choose [personal tasks</span><span class="p">](</span><span class="sx">things:///show?id=anytime&filter=@Me</span><span class="p">)</span>.
<span class="p">-</span> [ ] 📅 Check schedule.
<span class="p">-</span> [ ] 🕊 Make room for margin.
Tags: Rituals: Daily
</code></pre></div>
<p>This ritual gets done 50% of the time. I mostly plan my day the previous night in the Workday Shutdown ritual. This one is designed to help me remember what's important, fill the remaining plan with useful intention, and stay healthy.</p>
<h3 id="workday-shutdown-workdays-4p">🌇 Workday Shutdown (Workdays @ 4p)</h3>
<div class="highlight"><pre class="highlight markdown"><code><span class="gh"># 🗃 Workday Shutdown</span>
As a faithful steward, I put my work in order to be prepared for the next day, identifying the essential task for the next day.
<span class="p">
-</span> <span class="p">[</span><span class="nv"> ] Open the [Daily Note</span><span class="p">](</span><span class="sx">obsidian://advanced-uri?vault=wiki&commandname=Periodic%20Notes%3A%20Open%20daily%20note</span><span class="p">)</span>.
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 🕰 Review [time logs</span><span class="p">](</span><span class="sx">https://track.toggl.com/timer</span><span class="p">)</span> and time blocks. Mark misspent time on Calendar blocks.
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 📓 [Log tasks/projects</span><span class="p">](</span><span class="sx">things:///show?id=logbook</span><span class="p">)</span>.
<span class="p">-</span> [ ] 📝 Process notebook.
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 📨 Process [Drafts inboxes</span><span class="p">](</span><span class="sx">drafts://</span><span class="p">)</span>.
<span class="p">-</span> [ ] 📥 Dump your brain into the Inbox. Get it all out.
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 📤 Process [Things inbox</span><span class="p">](</span><span class="sx">things:///show?id=inbox</span><span class="p">)</span>. (:estimate, $focus, !modality, priority)
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 🗓 Time block [tomorrow</span><span class="p">](</span><span class="sx">things:///show?id=tomorrow</span><span class="p">)</span>.
<span class="p">-</span> [ ] 😴 Shut down. (Close. Speak. Shut. Trash. Door. Go home.)
<span class="p">-</span> [ ] 👨👩👧👦 Go live in Coram Deo this evening.
Tags: Rituals: Daily
</code></pre></div>
<p>This ritual is the rock of my whole system. I go through my inboxes, give all the straggling thoughts a home, log everything I intend to log, and leave the desk for the evening.</p>
<p>This I originally installed as a response to overworking after reading <a href="https://evantravers.com/articles/2019/02/09/review-deep-work-by-cal-newport/">Deep Work</a>, but it's expanded to be "manager time" to keep me on track.</p>
<h2 id="weekly-journal-weekly-yyyy-w-week-number-md">🗒️ Weekly: <code>journal/weekly/YYYY-W[week number].md</code></h2>
<div class="highlight"><pre class="highlight markdown"><code><span class="nn">---</span>
<span class="na">Goal1</span><span class="pi">:</span>
<span class="na">Goal2</span><span class="pi">:</span>
<span class="nn">---</span>
<span class="gh"># Weekly Review: date%3Agggg-%5BW%5Dww</span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">'admonition tldr'</span><span class="nt">><p></span>![[journal/yearly/2023#Theme]]<span class="nt"></p></span>
<span class="nt"></div></span>
<span class="gu">## Planning</span>
<span class="gu">## Review</span>
<span class="gu">### 👍:</span>
<span class="gu">### 👎:</span>
<span class="gu">### ✅:</span>
<span class="p">
---</span>
<span class="sb">```dataviewjs
let p = function(b) {
switch(b) {
case "": return ""; break;
case " ": return ""; break;
case 0: return "⚫"; break;
case 1: return "🔴"; break;
case 2: return "🟡"; break;
case 3: return "🟢"; break;
}
}
let f = function(d) {
return `[[${d.obsidianLink()}|${moment(d, 'YYYY-MM-DD').format("dd")}]]`;
}
let start = moment(dv.current().file.name, "gggg-[W]ww");
let end = start.clone().add(6, 'd');
dv.header(2, `Dailies: ${start.format("MM-DD")} - ${end.format("MM-DD")}`)
dv.table(
["📆", "🙏", "👫", "👨👩👧👦", "💪", "🖌", "💻", "🎯"], dv.pages('"journal/daily"')
.filter(d => moment(d.file.name).isBetween(start, end, undefined, '[]'))
.sort(d => d.file.day, 'asc')
.map(d => [f(d.file.link), p(d.follower), p(d.husband), p(d.father), p(d.health), p(d.create), p(d.work), p(d.plan)])
)
```</span>
</code></pre></div>
<p>This is the weekly template where all the weekly events show up. It rolls up the <a href="https://evantravers.com/articles/2022/09/12/journaling-and-reviews-in-obsidian/#weekly">daily ratings into something digestible</a>… it's pretty common for me to spot little patterns that I need to address.</p>
<p>The Goals at the top are found in the Quarterly note and roll up there eventually.</p>
<h3 id="weekly-review-fridays-4p">🗃️ Weekly Review (Fridays @ 4p)</h3>
<div class="highlight"><pre class="highlight markdown"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">'admonition info'</span><span class="nt">><p></span>Live <span class="nt"><em></span>coram deo<span class="nt"></em></span>. Plan this week in the presence of God.<span class="nt"></p></span>
<span class="nt"></div></span>
<span class="gu">## 📓 Prep</span>
<span class="p">
-</span> [ ] 🙏: Pray. Ask the Lord for wisdom and clarity on what is important eternally.
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 📓: Review journal and [daily notes</span><span class="p">](</span><span class="sx">obsidian://advanced-uri?vault=wiki&commandname=Periodic%20Notes%3A%20Open%20weekly%20note</span><span class="p">)</span>.
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] ✅: What did you [accomplish</span><span class="p">](</span><span class="sx">things:///show?id=logbook</span><span class="p">)</span>?
<span class="p">-</span> [ ] 💭: What can you learn from last week?
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 📈: Put logged successes in [OKR system in ADP</span><span class="p">](</span><span class="sx">https://workforcenow.adp.com/theme/index.html#/Myself/MyselfTabTalentCategoryPerformanceGoals</span><span class="p">)</span>.
<span class="p">-</span> [ ] 📝 Get Clear:
<span class="p"> -</span> [ ] Do a brain dump. Add any tasks or projects you come up with to the Things inbox.
<span class="p"> -</span> [ ] Process your physical inbox. Create tasks in Things for each item in your physical inbox that you want to take action on.
<span class="p"> -</span> [ ] Process your email inbox. Use Mail to Things to forward emails you need or want to take action on to your Things inbox.
<span class="p"> -</span> [ ] Process your Things inbox. Assign each task to an area or to a project. Tag appropriately. (:estimate, $focus, !categorize, priority)
<span class="p">-</span> [ ] Go through each of your projects. Use the checklist[^checklist].
<span class="p">[</span><span class="ss">^checklist</span><span class="p">]:</span><span class="err">
</span> <span class="sx">##</span> 📂 For Each Area:
<span class="p"> -</span> [ ] Are your projects in correct priority order?<span class="sb">
### 📁 For Each Project:
- [ ] Is this project still relevant?
- [ ] Can I delegate this project?
- [ ] Should I move this project to Someday?
- [ ] Are there are tasks I have already completed?
- [ ] Are there any tasks I want to delete?
- [ ] Am I happy with the structure of the project? E.g., should I add or change headings?
- [ ] Do all tasks with deadlines have the correct deadline set?
- [ ] Could I add useful notes to any tasks or to the project itself?
- [ ] Are any new tasks for this project not yet in Things?
- [ ] Should I convert any tasks to separate projects?
- [ ] Is there a clear 'next action' for this project? (If not, break down your projects or tasks into smaller tasks until there is a clear next action.)
- [ ] How can you serve and surprise in this area?
</span><span class="gu">## 📅 Plan</span>
<span class="p">
-</span> [ ] Look ahead at the week. Using Deadlines and Upcoming, examine the week and month to find tasks that need to be done, and space them out through the week.
<span class="p">-</span> [ ] Make time for important work. Create 4 hour strategic blocks and identify some buffer blocks.
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] Schedule in the most important task for the day. Using [Upcoming</span><span class="p">](</span><span class="sx">things:///show?id=upcoming&filter=%40Meazure%20Learning%2CEstimates</span><span class="p">)</span>, schedule the important task for the day.
</code></pre></div>
<p>Besides the Workday Shutdown, this is the other ritual that keeps everything running.</p>
<p>Parts of the list came from Peter Akkies, some of it is <a href="https://evantravers.com/articles/2023/01/27/weekly-reviews-in-things-app-using-shortcuts/">automated</a> now.</p>
<h3 id="family-weekly-fridays-7p">🫂 Family Weekly (Fridays @ 7p)</h3>
<div class="highlight"><pre class="highlight markdown"><code><span class="gh"># 📖 Weekly Meeting</span>
<span class="gu">## Rapport</span>
<span class="p">-</span> [ ] 🙏🏻 Prayer.
<span class="p">-</span> [ ] 👂 How are you in one word?
<span class="p"> -</span> [ ] Screen Time Check
<span class="gu">## What's the plan?</span>
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 🗓 Check [last week’s entry</span><span class="p">](</span><span class="sx">obsidian://advanced-uri?vault=wiki&commandid=periodic-notes%253Aprev-weekly-note</span><span class="p">)</span>.
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 📉 Check the [Monthly Plan</span><span class="p">](</span><span class="sx">obsidian://advanced-uri?vault=wiki&commandid=periodic-notes%253Aopen-monthly-note</span><span class="p">)</span>.
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 📈 Check our progress against [12WY Scoreboard</span><span class="p">](</span><span class="sx">obsidian://advanced-uri?vault=wiki&commandid=periodic-notes%253Aopen-quarterly-note</span><span class="p">)</span>.
<span class="gu">## What should we change?</span>
<span class="p">-</span> [ ] 🚦 Start / Stop / Continue:
<span class="p"> -</span> [ ] 🟢 What should we start?
<span class="p"> -</span> [ ] 🟡 What should we continue?
<span class="p"> -</span> [ ] 🔴 What should we stop?
<span class="p">-</span> [ ] 🪨 Big Rocks…
<span class="p"> -</span> [ ] Spiritual Leadership?
<span class="p"> -</span> [ ] School?
<span class="p"> -</span> [ ] Next House?
<span class="p"> -</span> [ ] Good Vibes Only?
<span class="p"> -</span> [ ] Leaving and Cleaving?
<span class="gu">## Follow through.</span>
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] ❓ Any [questions</span><span class="p">](</span><span class="sx">things:///show?id=anytime&filter=@Family</span><span class="p">)</span> to answer?
<span class="p">-</span> <span class="p">[</span><span class="nv"> ] 📆 [What are we committed to next week</span><span class="p">](</span><span class="sx">fantastical2://show?date=week</span><span class="p">)</span>?
<span class="p">-</span> [ ] 🧱 Set aside time blocks:
<span class="p"> -</span> [ ] Groceries and errands?
<span class="p"> -</span> [ ] Family-together time?
<span class="p"> -</span> [ ] Fellowship with other families?
<span class="p"> -</span> [ ] Time to work/talk together on the Big Problems?
<span class="p"> -</span> [ ] Projects? (Walking project dictation to unpack and delegate)
<span class="p">-</span> [ ] 💴 Budget Update.
<span class="gu">## Reset our purpose.</span>
<span class="p">-</span> [ ] ⛰ What are you going to look forward to this week?
<span class="p">-</span> [ ] 🤗 Express gratitude.
Tags: @Family, Rituals: Weekly
</code></pre></div>
<p>This ritual changes a good bit as my wife and I find new things to work on together. One recent adjustment is to have a <code>🪨 Big Rocks</code> header that contains nudges to check in on important conversations or projects.</p>
<h2 id="monthly-journal-monthly-yyyy-mm-md">🗒️ Monthly: <code>journal/monthly/YYYY-MM.md</code></h2>
<p>(Monthly doesn't have a format at the moment.)</p>
<h3 id="family-monthly-last-friday-of-month">🗓️ Family Monthly (Last Friday of Month)</h3>
<div class="highlight"><pre class="highlight markdown"><code><span class="gh"># 📆 Family Monthly Meeting</span>
The goal is to be through this in less than thirty minutes,[^format] in addition to the [[Family Weekly]].
<span class="p">
-</span> <span class="p">[</span><span class="nv"> ] 👈 Review the [last month's plan</span><span class="p">](</span><span class="sx">obsidian://advanced-uri?vault=wiki&commandid=periodic-notes%253Aprev-monthly-note</span><span class="p">)</span> and <span class="p">[</span><span class="nv">quarterly plan</span><span class="p">](</span><span class="sx">obsidian://advanced-uri?vault=wiki&commandid=periodic-notes%253Aopen-quarterly-note</span><span class="p">)</span>.
<span class="p">-</span> [ ] 📆 Look ahead at schedule for the month…
<span class="p">-</span> [ ] 🔨 Identify this month's most important event or project?
<span class="p">-</span> [ ] 🏆 Define success for the month. In this month…
<span class="p"> -</span> [ ] What is a task or routine that will help us work towards best possible week sometime this month?
<span class="p">-</span> [ ] 📈 What micro-adjustment do we want to make to our daily schedule?
<span class="p">-</span> [ ] ⛰ What Real Life Experience are we going to have this month?
<span class="p">-</span> [ ] 🧘 One way we can protect margin this month is…
<span class="p">-</span> [ ] 🏠 Prioritize home maintenance backlog.
<span class="p">-</span> [ ] 🗑️ Pick a declutter space and budget time.
<span class="p">-</span> [ ] Pick a "Deep Cleaning" day.
<span class="p">-</span> [ ] 💸 Create/adjust budget goals.
<span class="p">-</span> [ ] 🛑 Block off days to succeed at our projects…
Tags: @Sarah Travers, Rituals: Monthly
<span class="p">[</span><span class="ss">^format</span><span class="p">]:</span> <span class="sx">Taken</span> in part from Plan Your Year 2020.
</code></pre></div>
<p>Family Monthly is a touchstone for making sure we understand how crammed our current commitments are and make space for weekend projects like painting or tackling a closet.</p>
<h2 id="quarterly-journal-quarterly-yyyy-q-n-md">🗒️ Quarterly: <code>journal/quarterly/YYYY-Q[N].md</code></h2>
<div class="highlight"><pre class="highlight markdown"><code><span class="nn">---</span>
<span class="c1"># rate your outcomes from 1 to 5 in the following roles…</span>
<span class="na">roles</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Follower</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Husband</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Father</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Friends</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Work</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Hobbies</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Emotions</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Mental</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Health</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Rest</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Finances</span><span class="pi">:</span>
<span class="nn">---</span>
<span class="gu">## Plan</span>
<span class="gu">### Goals</span>
<span class="gu">#### Goal 1: _Goal_</span>
<span class="gu">#### Goal 2: _Goal_</span>
<span class="gu">## Review</span>
<span class="gu">### Where are you in each area of life?</span>
<span class="sb">```dataviewjs
// we have to sort somehow, so we convert the names to numbers and sort
const s = function(i) {
return Number(i.replace('-Q', ''));
}
const allQuarters =
dv.pages('"journal/quarterly"')
.array()
.sort(function(a, b) {
return s(a.file.name) - s(b.file.name);
})
let currentIndex = allQuarters.findIndex((q) => q.file.name == dv.current().file.name);
const quarters = allQuarters.slice(currentIndex - 3, currentIndex+1);
let datasets = [];
let trans = function(color) {
return color.replace(', 1)', ', 0.5)');
}
let colors = [
'rgba(105,105,105, 1)',
'rgba(169,169,169, 1)',
'rgba(211,211,211, 1)',
]
const purple = 'rgba(139,108,239, 1)';
let highlight = '#fff';
if (document.body.classList.contains('theme-light')) {
colors.reverse();
highlight = '#000';
}
colors.push(purple);
for (let i in quarters) {
let quarter = quarters[i];
if (quarter.hasOwnProperty('roles')) {
let values = quarter.roles.map(o => Object.values(o)).flat()
datasets.unshift({
label: quarter.file.name,
data: values,
fill: true,
backgroundColor: trans(colors[i]),
borderColor: colors[i],
pointBackgroundColor: colors[i],
pointBorderColor: highlight,
pointHoverBackgroundColor: highlight,
pointHoverBorderColor: colors[i]
})
}
}
let data = dv.current().roles
let keys = data.map(o => Object.keys(o)).flat()
const chartData = {
type: 'radar',
data: {
labels: keys,
datasets: datasets
},
options: {
scales: {
r: {
suggestedMin: 0,
suggestedMax: 5
}
}
}
}
window.renderChart(chartData, this.container);
```</span>
<span class="gu">### Scoreboard</span>
<span class="sb">```dataviewjs
let check = function(bool) {
if (bool) { return "✅" };
return "⛔";
}
let quarter = parseInt(dv.current().file.name.match(/Q\d/)[0].replace('Q', ''));
let year = dv.current().file.name.match(/\d{4}/)[0];
let start = (quarter-1)*13+1
dv.table(
["", "Week", "Goal 1", "Goal 2"], dv.pages('"journal/weekly"')
.filter(function(f) {
let num = f.file.name.match(new RegExp(`${year}-W(\\d+)`));
if (!num) { return false; }
num = parseInt(num[1]);
return num >= start && num < start + 12;
})
.sort(w => w.file.name, 'asc')
.map(w => [(parseInt(w.file.name.match(new RegExp(`${year}-W(\\d+)`))[1]) - start)+1, w.file.link, check(w["Goal1"]), check(w["Goal2"])])
)
```</span>
</code></pre></div>
<p>I've previously <a href="https://evantravers.com/articles/2023/03/28/obsidian-12wy-review-template/">written about this template</a>… it's pretty complicated. It rolls up the weekly reports on my 12WY goals and provides an opportunity to rate myself on my roles… and see whether my goals effected any change in those roles.</p>
<h3 id="12wy-every-12-weeks">📈 12WY (Every 12 weeks)</h3>
<div class="highlight"><pre class="highlight markdown"><code><span class="gh"># 🎯 12WY Review</span>
<span class="p">
-</span> <span class="p">[</span><span class="nv"> ] Open [12WY Note</span><span class="p">](</span><span class="sx">obsidian://advanced-uri?vault=wiki&commandid=periodic-notes%253Aopen-quarterly-note</span><span class="p">)</span>.
<span class="p">-</span> [ ] Go through week reviews in Obsidian.
<span class="p">-</span> [ ] What can you learn from 12WY?
<span class="p"> -</span> [ ] Rate your intentions on the Wheel of Life.
<span class="p">-</span> [ ] Talk to Sarah.
<span class="p">-</span> [ ] What are you going to do next 12WY differently?
<span class="p">-</span> [ ] What are your new goals for the new 12WY?
Tags: !Planning, :4h, Rituals: 12WY
</code></pre></div>
<p>This is the accompanying check list that goes with the Quarterly template. It's a work in progress. I could probably stand to re-read <a href="https://evantravers.com/articles/2021/05/29/review-12-week-year/">12 Week Year</a> and adjust.</p>
<h2 id="yearly-journal-yearly-yyyy-md">🗒️ Yearly <code>journal/yearly/YYYY.md</code></h2>
<p>No template at the moment… it keeps changing. </p>
<p>The past few years I've picked <a href="https://www.themesystem.com/">a yearly theme</a> and written it in a <code>## Theme</code> header near the top of the document. This gets referenced in the Weekly template above so that I see it all year long.</p>
<h3 id="yearly-review-year-end">🥂 Yearly Review (Year end)</h3>
<p>I traditionally journal, do <a href="https://evantravers.com/articles/2020/12/28/plan-your-year/">Plan Your Year</a>… just think and journal. It's come naturally to me. </p>
<p>It's funny because writing/journaling about my year is probably my first and oldest ritual. A lot of the ones above exist because I realized that the clarity of review and thinking was helping me get out of trouble… and once a year is too infrequent to stay out of trouble.</p>
<p>I try to focus on gratitude. I also try to count things that are important to me:</p>
<ul>
<li>good dinners with my wife.</li>
<li>vacations</li>
<li>weekend adventures with the kids.</li>
<li>meaningful conversations.</li>
<li>prayers answered.</li>
<li>books read.</li>
</ul>
<h2 id="part-b8a856d">…</h2>
<p>I repeat… don't copy my setup. Find one useful idea and run away. 🏃</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>There is a weird thing about measuring: you should weigh yourself daily, but pay attention to your weekly or monthly average. Most of us pay too much attention to the progress equivalent daily weight and give it more mindshare than we ought. <a href="#fnref1">↩</a></p>
</li>
<li id="fn2">
<p>First encountered in <a href="https://danluu.com/wat/">Dan Luu's Normalization of Deviance</a>: <a href="#fnref2">↩</a></p>
<blockquote>
<p>“Pay attention to weak signals” sure sounds like good advice, but how do we do it? Strong signals are few and far between, making them easy to pay attention to. Weak signals are abundant. How do we filter out the ones that aren't important? And how do we get an entire team or org to actually do it? These kinds of questions can't be answered in a generic way; this takes real thought. We mostly put this thought elsewhere.</p>
</blockquote>
<p>Weak Signals carry the same information as a Strong Signal but they usually manifest much earlier as little ripples… long before the tidal wave.</p>
<p>Weak Signals are very hard to recognize and learn from.</p>
</li>
</ol>
</div>
ZMK Added the Apple Globe Keyhttps://evantravers.com/articles/2023/11/09/zmk-added-the-apple-globe-key/2023-11-09T23:09:00+00:002023-11-09T17:10:10+00:00Evan Travers<p>I recently noticed that <a href="https://github.com/zmkfirmware/zmk/issues/947">ZMK added the "globe" key</a> from the Apple ecosystem to their key codes.</p>
<p>I threw it on my <a href="https://evantravers.com/articles/2022/04/19/review-corne-ish-zen/">Corne-ish Zen</a> and now I have access to <a href="https://thesweetsetup.com/global-keyboard-shortcuts-for-multitasking-in-ipados-15/">a bunch of useful power user shortcuts</a> on iPad and iOS. I particularly like starting dictation or...</p><p>I recently noticed that <a href="https://github.com/zmkfirmware/zmk/issues/947">ZMK added the "globe" key</a> from the Apple ecosystem to their key codes.</p>
<p>I threw it on my <a href="https://evantravers.com/articles/2022/04/19/review-corne-ish-zen/">Corne-ish Zen</a> and now I have access to <a href="https://thesweetsetup.com/global-keyboard-shortcuts-for-multitasking-in-ipados-15/">a bunch of useful power user shortcuts</a> on iPad and iOS. I particularly like starting dictation or going to the previous app.</p>
<p>I am using it as a <a href="https://zmk.dev/docs/behaviors/sticky-key">sticky modifier</a>, so I press it once and then press the next key. That lets me tuck it up in a layer, but you can use it however you want.</p>
<p>Here's <a href="https://github.com/evantravers/zmk-config/commit/8591c1022bd578681c26dd28a5fa4099eee6ab68">my commit</a>.</p>
Opportunity Solution Treeshttps://evantravers.com/articles/2023/10/25/opportunity-solution-trees/2023-10-25T15:05:00+00:002023-10-25T10:52:30+00:00Evan Travers<div class="admonition Summary"><p>An expert Mental Model unpacking how an experienced Product Manager connects Solutions to Outcomes and prioritizes Experiments. The tree broadens the horizon past the first Solution found, compares Opportunity-connected Solutions, and prioritizes Experiments...</p></div><div class='admonition Summary'><p>An expert Mental Model unpacking how an experienced Product Manager connects Solutions to Outcomes and prioritizes Experiments. The tree broadens the horizon past the first Solution found, compares Opportunity-connected Solutions, and prioritizes Experiments.</p>
</div>
<p><img src="/images/articles/2023/10/ost.png" alt="Opportunity Solution Tree"></p>
<h2 id="steps">Steps</h2>
<ol>
<li>Identify a clear <code>Desired Outcome</code>.</li>
<li>Product <code>Opportunities</code> to drive that outcome should emerge from user/market needs discovered during generative research.</li>
<li><code>Solutions</code> come from everywhere… but they should connect to an <code>Opportunity</code>.</li>
<li><code>Experiment</code> to evaluate and evolve <code>Solutions</code>.<sup id="fnref1"><a href="#fn1">1</a></sup></li>
</ol>
<h2 id="reframing-for-wide-discovery">Reframing for Wide Discovery</h2>
<p>Torres was inspired by Reframing Questions<sup id="fnref2"><a href="#fn2">2</a></sup> to broaden stakeholder's mindset with an additional logic layer of the <code>Opportunity</code>. As humans we typically fixate on the first discovered <code>Solution</code>, yet we actually want as <em>many</em> to investigate and explore as possible.<sup id="fnref3"><a href="#fn3">3</a></sup></p>
<h2 id="prioritization-through-connection">Prioritization through Connection</h2>
<p>By tying the <code>Solution</code> to an <code>Opportunity</code> (the user problem it is attempting to solve) you can reason more clearly and prioritize ruthlessly.</p>
<p>Attempting a broad "whether or not" decision on a particular <code>Solution</code> can be a stalemate.<sup id="fnref4"><a href="#fn4">4</a></sup></p>
<p>Using a Opportunity Solution Tree:</p>
<p>We can quickly discard <code>Solutions</code> that aren't tied to an <code>Opportunity</code> that drives our <code>Desired Outcome</code>.</p>
<p>Next we can bound our decision to "compare and contrast" <code>Solutions</code> related to the same high-value and real <code>Opportunities</code>. We then craft <code>Experiments</code> to measure the effectiveness of these <code>Solutions</code>, guided by the <code>Opportunity</code>'s framing to narrow our measurements.</p>
<p>Related:</p>
<ul>
<li>Introduced to me by Dave Masom of <a href="https://conserv.io">Conserv</a></li>
<li>From <em>Continuous Discovery Habits</em> by Teresa Torres</li>
<li>Source: <a href="https://evantravers.com/articles/2020/07/09/outcomes-over-outputs/">https://www.producttalk.org/opportunity-solution-tree/</a> and a couple related blog posts and podcasts.</li>
<li><code>Desired Outcome</code> -> <code>Outcomes</code> :: <code>Solutions</code> -> <code>Outputs</code> (<a href="https://evantravers.com/articles/2020/07/09/outcomes-over-outputs/">Outcomes over Outputs</a>) Opportunity Solution Trees is a lens on these related concepts. The exercises and visuals can untangle an existing concept space that may be too solution heavy and needs a tie-breaker.</li>
</ul>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>It feels like <a href="https://evantravers.com/articles/2019/07/31/books-and-links-july/#lean-ux-by-jeff-gothelf-and-josh-seiden">Lean UX by Jeff Gothelf</a> would work well here. <a href="#fnref1">↩</a></p>
</li>
<li id="fn2">
<p>Bernie Roth used a powerful question to drive past Buyers are Liars to get at the truth. After asking people what they wanted, he asked <a href="#fnref2">↩</a></p>
<blockquote>
<p>If you had whatever you wrote down today, what would that do for you?</p>
</blockquote>
<p>He drove past the expressed <code>Solution</code> to discover the true <code>Desired Outcome</code>. Exploring past the Output to determine the Outcome. (<a href="https://evantravers.com/articles/2020/07/09/outcomes-over-outputs/">Outcomes over Outputs</a>)</p>
<p>He then broadened that initial <code>Solution</code> by asking</p>
<blockquote>
<p>How else might you {accomplish the same goal}?</p>
</blockquote>
<p>This prevented being fixated on the first potential path and creating a tree of paths to explore. He was reframing the expressed <code>Solution</code> desire into a <code>Desired Outcome</code>… the truth of what the attendee was attempting to accomplish</p>
</li>
<li id="fn3">
<p>Similar to Discovery step of Double Diamond Design Process. The Double Diamond Design Process describes a double sequence of Divergent and Convergent Thinking. It's common for people thinking about Service Design. <a href="#fnref3">↩</a></p>
<p><img src="/images/articles/2023/10/double-diamond.png" alt="double diamond"></p>
<p>According to <em>Org Design for Design Orgs</em> by Peter Merholz, Kristin Skinner, the Execution step is really a series of smaller Execution steps (iteration and implementation). How the Plan is framed determines how effective the Execution step will be.</p>
</li>
<li id="fn4">
<blockquote>
<p>We can argue all day, but we won’t make any progress if we spend all of our time debating the merits of each solution. <a href="#fnref4">↩</a></p>
<p>Instead, we want to consider which problem each solution solves and instead make a decision about which problem leads to a more valuable opportunity.</p>
</blockquote>
<p>Source: <a href="https://www.producttalk.org/2016/08/opportunity-solution-tree/">https://www.producttalk.org/2016/08/opportunity-solution-tree/</a></p>
</li>
</ol>
</div>
Ripples Across the Webhttps://evantravers.com/articles/2023/10/05/ripples-across-the-web/2023-10-05T13:36:00+00:002023-10-05T08:38:08+00:00Evan Travers<em>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="16" width="16">
# <path fill="currentColor" d="M 4 4.44 v 2.83 c 7.03 0 12.73 5.7 12.73 12.73 h 2.83 c 0 -8.59 -6.97 -15.56 -15.56 -15.56 Z m 0 5.66 v 2.83 c 3.9 0 7.07 3.17 7.07 7.07 h 2.83 c 0 -5.47 -4.43 -9.9 -9.9 -9.9 Z M 6.18 15.64 A 2.18 2.18 0 0 1 6.18 20 A 2.18 2.18 0 0 1 6.18 15.64"></path>
</svg>
This is an RSS-only post. It's a secret! Read more about <a href="https://daverupert.com/rss-club/">RSS Club</a>.
</em><p>A fun sequence of events…</p>
<ol>
<li>I saw a novel question on my For You page, <a href="https://twitter.com/evantravers/status/1688651100973375488">searched Google domains and responded</a>.</li>
<li>Over the following week I got notifications as Benten built <a href="https://footer.design">footer.design</a>
</li>
<li>Yesterday <a href="https://robinrendle.com/notes/footer.design-/">Robin noted it</a>. I saw it on RSS and giggled.</li>
<li>sidebar.io...</li>
</ol><em>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="16" width="16">
# <path fill="currentColor" d="M 4 4.44 v 2.83 c 7.03 0 12.73 5.7 12.73 12.73 h 2.83 c 0 -8.59 -6.97 -15.56 -15.56 -15.56 Z m 0 5.66 v 2.83 c 3.9 0 7.07 3.17 7.07 7.07 h 2.83 c 0 -5.47 -4.43 -9.9 -9.9 -9.9 Z M 6.18 15.64 A 2.18 2.18 0 0 1 6.18 20 A 2.18 2.18 0 0 1 6.18 15.64"></path>
</svg>
This is an RSS-only post. It's a secret! Read more about <a href="https://daverupert.com/rss-club/">RSS Club</a>.
</em><p>A fun sequence of events…</p>
<ol>
<li>I saw a novel question on my For You page, <a href="https://twitter.com/evantravers/status/1688651100973375488">searched Google domains and responded</a>.</li>
<li>Over the following week I got notifications as Benten built <a href="https://footer.design">footer.design</a></li>
<li>Yesterday <a href="https://robinrendle.com/notes/footer.design-/">Robin noted it</a>. I saw it on RSS and giggled.</li>
<li>sidebar.io <a href="https://sidebar.io/date/2023-10-05">reported it today</a>.</li>
</ol>
<p>Benten did all the work, but it <strong>is</strong> fun being a tiny part of pushing it over the edge.</p>
Achievement 🔓 : Mentioned in Whitepaperhttps://evantravers.com/articles/2023/10/04/achievement-unlocked-mentioned-in-whitepaper/2023-10-04T19:19:00+00:002023-10-04T14:37:16+00:00Evan Travers<em>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="16" width="16">
# <path fill="currentColor" d="M 4 4.44 v 2.83 c 7.03 0 12.73 5.7 12.73 12.73 h 2.83 c 0 -8.59 -6.97 -15.56 -15.56 -15.56 Z m 0 5.66 v 2.83 c 3.9 0 7.07 3.17 7.07 7.07 h 2.83 c 0 -5.47 -4.43 -9.9 -9.9 -9.9 Z M 6.18 15.64 A 2.18 2.18 0 0 1 6.18 20 A 2.18 2.18 0 0 1 6.18 15.64"></path>
</svg>
This is an RSS-only post. It's a secret! Read more about <a href="https://daverupert.com/rss-club/">RSS Club</a>.
</em><p>I love getting emails from y'all… but last month I got a unique one:</p>
<blockquote>
<p>I am currently investigating the presence of humor in software. […]</p>
<p>We noticed this post on your website where you mention using lolcommits for <a href="https://evantravers.com/articles/2019/11/21/making-a-book-using-ruby/">your (rather creative!) personal...</a></p>
</blockquote><em>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="16" width="16">
# <path fill="currentColor" d="M 4 4.44 v 2.83 c 7.03 0 12.73 5.7 12.73 12.73 h 2.83 c 0 -8.59 -6.97 -15.56 -15.56 -15.56 Z m 0 5.66 v 2.83 c 3.9 0 7.07 3.17 7.07 7.07 h 2.83 c 0 -5.47 -4.43 -9.9 -9.9 -9.9 Z M 6.18 15.64 A 2.18 2.18 0 0 1 6.18 20 A 2.18 2.18 0 0 1 6.18 15.64"></path>
</svg>
This is an RSS-only post. It's a secret! Read more about <a href="https://daverupert.com/rss-club/">RSS Club</a>.
</em><p>I love getting emails from y'all… but last month I got a unique one:</p>
<blockquote>
<p>I am currently investigating the presence of humor in software. […]</p>
<p>We noticed this post on your website where you mention using lolcommits for <a href="https://evantravers.com/articles/2019/11/21/making-a-book-using-ruby/">your (rather creative!) personal project</a>[…]</p>
<p>Would you be available for meeting with us online to discuss it further, and for sharing your insight on humor in software?</p>
</blockquote>
<p>I had the opportunity to chat with them… and now I'm mentioned in <a href="https://drive.google.com/drive/folders/1gtBLwHIdQ3klP4TYq0PMw9djVc4R9Frd?usp=sharing">an academic whitepaper</a>.</p>
<p>It's neat (and very random!) what can happen when you work in public.</p>
Converting H1 to Inline Notes for Evergreen Noteshttps://evantravers.com/articles/2023/09/29/converting-h1-to-inline-notes-for-evergreen-notes/2023-09-29T15:26:00+00:002023-09-29T10:27:00+00:00Evan Travers<p>My friend Joschua wrote recently about the <a href="https://joschua.io/posts/2023/09/16/three-tips-for-better-obsidian-notes/#2-enable-inline-titles">virtues of Inline Titles in note-making</a>. I've been solidly on the "Title & H1" camp for a while, but I've also been manually keeping them synced.</p>
<p>I was asking Joschua what he did to migrate his notes over...</p><p>My friend Joschua wrote recently about the <a href="https://joschua.io/posts/2023/09/16/three-tips-for-better-obsidian-notes/#2-enable-inline-titles">virtues of Inline Titles in note-making</a>. I've been solidly on the "Title & H1" camp for a while, but I've also been manually keeping them synced.</p>
<p>I was asking Joschua what he did to migrate his notes over to inline titles… and he replied he's been manually changing them as he encounters it in his notes. So I wrote him a script:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="c1"># work through every file ending in .md</span>
<span class="no">Dir</span><span class="p">.</span><span class="nf">glob</span><span class="p">(</span><span class="s2">"./**/*.md"</span><span class="p">).</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">file</span><span class="o">|</span>
<span class="c1"># extract the left part of the filename</span>
<span class="n">filename</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">basename</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="s1">'.md'</span><span class="p">)</span>
<span class="c1"># read the file</span>
<span class="n">content</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
<span class="c1"># split the file into an array of strings</span>
<span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="p">.</span><span class="nf">lines</span>
<span class="c1"># find a line that matches the filename</span>
<span class="n">matching_title</span> <span class="o">=</span> <span class="n">lines</span><span class="p">.</span><span class="nf">find_index</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="o">|</span> <span class="n">l</span><span class="p">.</span><span class="nf">match</span> <span class="sr">/^# </span><span class="si">#{</span><span class="n">filename</span><span class="si">}</span><span class="sr">\s*$/</span> <span class="p">}</span>
<span class="k">if</span> <span class="n">matching_title</span> <span class="k">then</span>
<span class="c1"># find block of empty lines</span>
<span class="n">blank_lines</span> <span class="o">=</span> <span class="n">matching_title</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">while</span> <span class="n">lines</span><span class="p">[</span><span class="n">blank_lines</span><span class="p">]</span> <span class="ow">and</span> <span class="n">lines</span><span class="p">[</span><span class="n">blank_lines</span><span class="p">].</span><span class="nf">match</span><span class="p">(</span><span class="sr">/^\s*$/</span><span class="p">)</span> <span class="k">do</span>
<span class="n">blank_lines</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="c1"># remove the header matching the title and following blank lines</span>
<span class="n">lines</span><span class="p">.</span><span class="nf">slice!</span><span class="p">(</span><span class="n">matching_title</span><span class="o">..</span><span class="n">blank_lines</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="no">File</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="n">lines</span><span class="p">.</span><span class="nf">join</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div>
<p>(from <a href="https://gist.github.com/evantravers/dbe040f37c3b10fcf82f5296435a2fa1">headers.rb</a>)</p>
<div class='admonition warning'><p>This script is destructive and changes your files without warning!</p>
</div>
<p>I would counsel only running it on a backed-up folder if you are comfortable with scripts. </p>
<p>If this transformation is something you want and you need help, you can <a href="/contact">message me</a>. </p>
<p>If there was enough interest, I'm sure the basic logic could be translated to JavaScript and turned into a plugin for folks that want to embrace inline titles in their Obsidian notes.</p>
Bug Huntinghttps://evantravers.com/articles/2023/09/27/bug-hunting/2023-09-27T16:10:00+00:002023-09-27T11:42:32+00:00Evan Travers<p>Talking with some new colleagues at work, this old programming war story from a decade ago came to mind. My memory on all the details is fuzzy<sup id="fnref1"><a href="#fn1">1</a></sup> but here's what I recall:</p>
<p>I was working for a web development company doing a lot of local e-commerce....</p><p>Talking with some new colleagues at work, this old programming war story from a decade ago came to mind. My memory on all the details is fuzzy<sup id="fnref1"><a href="#fn1">1</a></sup> but here's what I recall:</p>
<p>I was working for a web development company doing a lot of local e-commerce. Everything was on FTP, one staging server and one live server. While I was using git for personal projects (even though the computer science department in college didn't teach it!) I tried to evangelize it at work, but people thought it was needlessly complex and time-wasting. 🤷</p>
<p>I did a lot of support tickets and content changes for clients, but had somewhat specialized in a complicated wedding registry script that we often sold to e-commerce clients.</p>
<p>That day I got a familiar tasking: an older customer wanted the additional feature I had programmed for a newer client.</p>
<p>I copied the newer registry file over to the client folder, updated the client-specific IDs in the SQL queries, added the feature, refreshed my browser and clicked around to dev test my work. I logged my time, and sent the URL over to the project manager to say I was done, and moved on to the next support ticket.</p>
<p>My project manager Google Chat'd me: "hey man, this isn't done." Annoyed, I dropped the task I was now working and clicked back over and sure enough the page was broken. I opened up the file, scanned through and made all the changes again. This time I browser tested in both my browser and the project manager's preferred variant, and marked it as done. After lunch I got another message: "Still broken."</p>
<p>Over the next few days this loop continued. I'd fix the bug, it would work for me, I'd send it off to the PM, and he would lightly scold me for not finishing my work. I'd wrap up whatever I was then fixing, take another stab at the changes, and the cycle would continue.</p>
<p>Needless to say, my project manager and I were both getting really annoyed with each other.</p>
<p>I was encountering some kind of bug… but I was having a hard time determining what was breaking. As a junior developer there were a lot of systems at play that I didn't understand: a shared database with live and changing data, CSS and HTML bugs between browsers, and a complex homebrew CMS with no documentation. I didn't yet have the system understanding to determine what system drove what behavior… so I tried brute force for far too long.</p>
<p>Finally I sat down, stopped multitasking, and slowly worked through the problem, instrumenting the solution with print-to-screen logging and comments… until I yelled with victory and anger.</p>
<p>I walked into the next room, unplugged a computer, waited six hours, and the application was fixed permanently.</p>
<h2 id="part-b8a856d">…</h2>
<p>While working on the logging I saw that the file had magically reverted back to the start of the process. Using my FTP client I inspected the file and discovered that the last modified username was from an employee that had been on vacation that week…</p>
<p>I was right, and my project manager was right. I had correctly installed the registry, but my changes had not stayed on the server.</p>
<p>It turns out that the other developer had opened the whole folder in Homesite+ on his work laptop and then left. He had set his editor to autosave his open work every five minutes or so… and therefore his sleeping computer was clobbering my work, usually right around the time I would send it to my Project Manager.</p>
<p>With permission from the boss, I unplugged his laptop, waited for the battery to die, then finished the feature in seconds… I had already written it a hundred times already.</p>
<p>After this horribleness… I learned how to use <a href="https://github.com/git-ftp/git-ftp">git-ftp</a> and never lost my work again… even though the company never used git while I was there.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>I wasn't then the journaling freak that I am now… <a href="#fnref1">↩</a></p>
</li>
</ol>
</div>
Export Things to Todoisthttps://evantravers.com/articles/2023/09/25/export-things-to-todoist/2023-09-25T13:30:00+00:002023-09-25T20:43:59+00:00Evan Travers<div class="admonition download">
<p><a href="/images/articles/2023/09/Things%20%E2%9E%A1%EF%B8%8F%20Todoist.zip">📥 Download Things ➡️ Todoist.shortcut</a></p>
</div>
<p>Todoist recently rolled out <a href="https://todoist.com/help/articles/5332270837276">a new version featuring task duration and therefore time-blocking</a>. 🤯</p>
<p>So I've been slaving over a crazy Shortcut to export my Things data to Todoist. 🤷 The pseudocode is basically...</p><div class='admonition download'><p><a href="/images/articles/2023/09/Things%20%E2%9E%A1%EF%B8%8F%20Todoist.zip">📥 Download Things ➡️ Todoist.shortcut</a></p>
</div>
<p>Todoist recently rolled out <a href="https://todoist.com/help/articles/5332270837276">a new version featuring task duration and therefore time-blocking</a>. 🤯</p>
<p>So I've been slaving over a crazy Shortcut to export my Things data to Todoist. 🤷 The pseudocode is basically:</p>
<div class="highlight"><pre class="highlight plaintext"><code>Get API key
Make Lookup dictionary
For each Area in Things:
Create Todoist Project
Store Things ID -> Todoist Project ID in Lookup
For each Project in Area:
Create Todoist Project with Area ID as Parent
Store Things ID -> Todoist Project ID in Lookup
For each Header in Project:
Create Todoist Section with Project as Parent
Store Header Name -> Section ID in Lookup
For each To-Do:
If has a Header:
Create Todoist Task using the Lookup to find Header's Section ID
Set Task ID to returned Todoist Task ID
Else
If has a Parent:
Find Parent ID -> Todoist Project ID using Lookup
Create Todoist Task with Parent ID
Set Task ID to returned Todoist Task ID
Else
Create Todoist Task
Set Task ID to returned Todoist Task ID
If Things To-Do has Deadline:
Update Task ID with Deadline
If Things To-Do has When:
Create "Start: Task" sub-task with Parent -> Task ID
If Things To-Do has Checklist:
For each item in Checklist:
Create Sub-Task with Parent -> Task ID
</code></pre></div>
<p>As complex as that is... <a href="/images/articles/2023/09/shortcut.JPG">it's worse in the Shortcuts editor.</a></p>
<p>Programming in Shortcuts sometimes feels like parenting a toddler: you have to say the same things multiple times, and most of the time neither you nor the child can remember if you've already dealt with this issue. I <em>swear</em> Shortcuts destroys or forgets a nested variable at random.</p>
<p>And the scroll-jacking! If Shortcuts would stop yanking my screen around! But I digress:</p>
<p>If nothing else… some things I learned:</p>
<ul>
<li>Don't. Copy. Actions. With. Variables. 😤</li>
<li>Use Reveal on nested variables to ensure it is pointed at the right objects.</li>
</ul>
<p>The export currently:</p>
<ul>
<li>translates Areas, Projects, and Headers into nested Projects with Sections</li>
<li>imports:
<ul>
<li>Title and Content</li>
<li><strike>Tags</strike> (For some reason, the Shortcut Export <em>wipes</em> this out every time!)</li>
<li>When (as a meta-start task)</li>
<li>Deadline</li>
<li>Checklists</li>
</ul></li>
</ul>
<p>It does not:</p>
<ul>
<li>save Project notes</li>
<li>handle repetition schedules</li>
<li>Reminders</li>
<li>Evening</li>
<li>import Logged projects or tasks</li>
<li>handle Headers with the same string in different Projects. I just renamed mine.</li>
</ul>
<p>It runs relatively quickly on my small-ish database of 200ish active tasks. It has to do a lot of little API calls to function, so it's possible you may run into Todoist's rate limit with a larger database. </p>
<h2 id="install">Install</h2>
<ol>
<li>Download the Shortcut here: <a href="/images/articles/2023/09/Things%20%E2%9E%A1%EF%B8%8F%20Todoist.zip">Things ➡️ Todoist.zip</a></li>
<li>Be on the <a href="https://todoist.com/pricing">Pro plan of Todoist</a>.</li>
<li><a href="https://todoist.com/help/articles/find-your-api-token">Get your API key</a> for Todoist ready.</li>
<li>Run the shortcut, pasting in the API when prompted.</li>
<li>Reply "Always" when Shortcuts asks permission to use Things data.</li>
<li>Profit.</li>
</ol>
<h2 id="part-b8a856d">…</h2>
<p>Shortcuts are finicky… but if you run into any problems, <a href="/contact">drop me a line</a> and I'll try to help.</p>
You Should Use: Excalidrawhttps://evantravers.com/articles/2023/08/22/you-should-use-excalidraw/2023-08-22T16:17:00+00:002023-08-22T11:56:42+00:00Evan Travers<p><img alt='' src='https://evantravers.com/images/articles/2023/08/cycle.png'></p><p><a href="https://excalidraw.com/">Excalidraw</a> is a free whiteboarding app I use constantly. Its expressive but simple tooling focuses on collaboration and sense-making instead of obsessing with perfection. It's simply the best.</p>
<h2 id="simple-tools">Simple Tools</h2>
<p><img src="/images/articles/2023/08/tools.png" alt="tools"></p>
<p>If you can hold a marker, you can use...</p><p><img alt='' src='https://evantravers.com/images/articles/2023/08/cycle.png'></p><p><a href="https://excalidraw.com/">Excalidraw</a> is a free whiteboarding app I use constantly. Its expressive but simple tooling focuses on collaboration and sense-making instead of obsessing with perfection. It's simply the best.</p>
<h2 id="simple-tools">Simple Tools</h2>
<p><img src="/images/articles/2023/08/tools.png" alt="tools"></p>
<p>If you can hold a marker, you can use a whiteboard. It's a level playing field. Excalidraw is mouse-centric but usable on a touch screen. If you have a screen you can participate.</p>
<h2 id="sharing">Sharing</h2>
<p>It has the simplest security model. You can share a unique link, anyone with that link can edit anything. It's straightforward and a delight after doing the permissions dance with so many other tools.</p>
<h2 id="keyboard-shortcuts">Keyboard Shortcuts</h2>
<p>As you may know, I <a href="https://evantravers.com/articles/tags/keyboards/">love my keyboard shortcuts</a> and it has really smart shortcuts covering most of it't shortcuts covering most of it's tooling. If nothing else, <code>⌘⌥-C</code> to copy your selection to clipboard as PNG is perfect for sharing a quick thought in a chat or pasting into a Confluence wiki.</p>
<h2 id="open-source">Open Source</h2>
<p><img src="/images/articles/2023/08/tolerance.png" alt="obsidian"></p>
<p>Open-source means that I can use a plugin to embed Excalidraw in my Obsidian note-taking app as editable vector illustrations for my notes… I can even modify them on my phone or iPad.</p>
<h2 id="part-b8a856d">…</h2>
<p>Give it a try next time you find yourself waving your hands in the air on a video-call.</p>
Visualizing Bookwormhttps://evantravers.com/articles/2023/08/19/visualizing-bookworm/2023-08-19T14:19:00+00:002023-08-20T17:39:36+00:00Evan Travers<p><img alt='' src='https://evantravers.com/images/articles/2023/08/livebook.png'></p><div class="highlight"><pre class="highlight elixir"><code><span class="no">Mix</span><span class="o">.</span><span class="n">install</span><span class="p">([</span>
<span class="p">{</span><span class="ss">:req</span><span class="p">,</span> <span class="s2">"~> 0.3.0"</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:floki</span><span class="p">,</span> <span class="s2">"~> 0.34.0"</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:kino_explorer</span><span class="p">,</span> <span class="s2">"~> 0.1.4"</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:libgraph</span><span class="p">,</span> <span class="s2">"~> 0.16.0"</span><span class="p">}</span>
<span class="p">])</span>
</code></pre></div>
<!-- livebook:{"output":true} -->
<h2 id="why">🤔 Why?</h2>
<p><a href="https://bookworm.fm/">Bookworm</a> is one of my favorite podcasts. Like many listeners I use it as a filter to find quality books to add to...</p><p><img alt='' src='https://evantravers.com/images/articles/2023/08/livebook.png'></p><div class="highlight"><pre class="highlight elixir"><code><span class="no">Mix</span><span class="o">.</span><span class="n">install</span><span class="p">([</span>
<span class="p">{</span><span class="ss">:req</span><span class="p">,</span> <span class="s2">"~> 0.3.0"</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:floki</span><span class="p">,</span> <span class="s2">"~> 0.34.0"</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:kino_explorer</span><span class="p">,</span> <span class="s2">"~> 0.1.4"</span><span class="p">},</span>
<span class="p">{</span><span class="ss">:libgraph</span><span class="p">,</span> <span class="s2">"~> 0.16.0"</span><span class="p">}</span>
<span class="p">])</span>
</code></pre></div>
<!-- livebook:{"output":true} -->
<h2 id="why">🤔 Why?</h2>
<p><a href="https://bookworm.fm/">Bookworm</a> is one of my favorite podcasts. Like many listeners I use it as a filter to find quality books to add to my queue. I love how the hosts don't just read but engage with the material, holding each other accountable with takeaways and challenges.</p>
<p>A recurring joke in Bookworm is how many times they refer to <a href="https://bookworm.fm/42/">Ep. 42, How To Read a Book by Mortimer Adler</a>. The last few times they've made that joke I've wanted to actually visualize that graph. I know Mike loves Obsidian, I love Elixir and have been learning <a href="https://livebook.dev/">Livebook</a>… let's play!</p>
<p>(Livebook is a markdown-based workbook system (similar to Jupyter notebooks) that uses erlang/elixir. This blog post is mostly written in Livebook and ran in Livebook. I exported the content and output, added some screenshots, and here we are! You could actually take the markdown source of this post and open it in Livebook. Pretty cool.)</p>
<h2 id="scraping">🎣 Scraping</h2>
<p>The first thing is to scrape the pages for processing and evaluation. One lovely thing about Livebook is that if you structure each code block separately, it intelligently memoizes each variable and only re-evaluates them if the structure changes.</p>
<p>Previously, I had used <a href="https://github.com/edgurgel/httpoison">HTTPoison</a> to do some downloading, but my friend <a href="http://megalithic.io">Seth</a> has recommended <a href="https://github.com/wojtekmach/req">Req</a>, so let's give that a whirl.</p>
<p>Adding the following to the setup:</p>
<!-- livebook:{"force_markdown":true} -->
<div class="highlight"><pre class="highlight elixir"><code><span class="no">Mix</span><span class="o">.</span><span class="n">install</span><span class="p">([</span>
<span class="p">{</span><span class="ss">:req</span><span class="p">,</span> <span class="s2">"~> 0.3.0"</span><span class="p">}</span>
<span class="p">])</span>
</code></pre></div>
<p>Now I can see if I can hit the opening page:</p>
<div class="highlight"><pre class="highlight elixir"><code><span class="no">Req</span><span class="o">.</span><span class="n">get!</span><span class="p">(</span><span class="s2">"https://bookworm.fm/"</span><span class="p">)</span><span class="o">.</span><span class="n">body</span>
</code></pre></div>
<!-- livebook:{"output":true} -->
<div class="highlight"><pre class="highlight html"><code>"<span class="cp"><!DOCTYPE html></span>\n<span class="nt"><html</span> <span class="na">lang=</span><span class="s">\"en-US\"</span> <span class="na">xmlns:og=</span><span class="s">\"http://ogp.me/ns#\""</span> <span class="err"><</span><span class="nt">></span> …
</code></pre></div>
<p>Success! Let's be a little more intentional.</p>
<p>Bookworm's website is structured logically. Every episode will be in the form <code>https://bookworm.fm/1/</code> up to <code>https://bookworm.fm/<current>/</code>. The most recent post is the top <code><h2></code> on the page.</p>
<!-- livebook:{"break_markdown":true} -->
<p>Now I can hone the CSS selector I need to get the most current link. What's really nice (again) about Livebook is that as I rerun the code over and over… I'm not pummeling my internet friends' server with requests. Livebook's cells have localized and memoized all the variables.</p>
<div class="highlight"><pre class="highlight elixir"><code><span class="n">most_recent_episode_number</span> <span class="o">=</span>
<span class="no">Req</span><span class="o">.</span><span class="n">get!</span><span class="p">(</span><span class="ss">url:</span> <span class="s2">"https://bookworm.fm/"</span><span class="p">)</span><span class="o">.</span><span class="n">body</span>
<span class="o">|></span> <span class="no">Floki</span><span class="o">.</span><span class="n">parse_document!</span><span class="p">()</span>
<span class="o">|></span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"h2 a"</span><span class="p">)</span>
<span class="o">|></span> <span class="no">List</span><span class="o">.</span><span class="n">first</span><span class="p">()</span>
<span class="o">|></span> <span class="no">Floki</span><span class="o">.</span><span class="n">attribute</span><span class="p">(</span><span class="s2">"href"</span><span class="p">)</span>
<span class="o">|></span> <span class="no">List</span><span class="o">.</span><span class="n">first</span><span class="p">()</span>
<span class="o">|></span> <span class="no">String</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">"/"</span><span class="p">,</span> <span class="ss">trim:</span> <span class="no">true</span><span class="p">)</span>
<span class="o">|></span> <span class="no">List</span><span class="o">.</span><span class="n">last</span><span class="p">()</span>
<span class="o">|></span> <span class="no">String</span><span class="o">.</span><span class="n">to_integer</span><span class="p">()</span>
</code></pre></div>
<!-- livebook:{"output":true} -->
<div class="highlight"><pre class="highlight plaintext"><code>176
</code></pre></div>
<p>☝️ This is a super gross way to do this, but I can fix it later.</p>
<p>Anyway. Excelsior!</p>
<div class="highlight"><pre class="highlight elixir"><code><span class="n">raw_episodes</span> <span class="o">=</span>
<span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="n">most_recent_episode_number</span><span class="p">,</span> <span class="k">fn</span> <span class="n">num</span> <span class="o">-></span>
<span class="n">url</span> <span class="o">=</span> <span class="s2">"https://bookworm.fm/</span><span class="si">#{</span><span class="n">num</span><span class="si">}</span><span class="s2">/"</span>
<span class="p">%{</span>
<span class="c1"># going to be important later! (basically the private key for the episode)</span>
<span class="ss">url:</span> <span class="n">url</span><span class="p">,</span>
<span class="ss">request:</span> <span class="no">Req</span><span class="o">.</span><span class="n">get!</span><span class="p">(</span><span class="ss">url:</span> <span class="n">url</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">end</span><span class="p">)</span>
</code></pre></div>
<!-- livebook:{"output":true} -->
<div class="highlight"><pre class="highlight html"><code>[
%{
request: %Req.Response{
status: 200,
headers: [
{"connection", "keep-alive"},
{"content-length", "13177"},
…
{"x-fw-type", "FLYWHEEL_BOT"}
],
body: "<span class="cp"><!DOCTYPE html></span>\n<span class="nt"><html</span> <span class="na">lang=</span><span class="s">\"en-US\""</span> <span class="err"><</span><span class="nt">></span> …,
private: %{}
},
url: "https://bookworm.fm/1/"
},
%{…},
…
]
</code></pre></div>
<p>The first time I tried this, I forgot the trailing slash at the end of the URL and was getting tons of redirects (I'm guessing to the URI with the trailing slash.) 🤷</p>
<p>Let's do some cleanup on this… we don't care about all the html, just the bit inside the main area.</p>
<div class="highlight"><pre class="highlight elixir"><code><span class="n">episodes</span> <span class="o">=</span>
<span class="n">raw_episodes</span>
<span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="k">fn</span> <span class="p">%{</span><span class="ss">request:</span> <span class="n">request</span><span class="p">}</span> <span class="o">=</span> <span class="n">episode</span> <span class="o">-></span>
<span class="n">floki_article</span> <span class="o">=</span>
<span class="n">request</span><span class="o">.</span><span class="n">body</span>
<span class="o">|></span> <span class="no">Floki</span><span class="o">.</span><span class="n">parse_fragment!</span><span class="p">()</span>
<span class="o">|></span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"article"</span><span class="p">)</span>
<span class="n">links</span> <span class="o">=</span>
<span class="n">floki_article</span>
<span class="o">|></span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"a"</span><span class="p">)</span>
<span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="k">fn</span> <span class="n">link</span> <span class="o">-></span>
<span class="n">link</span>
<span class="o">|></span> <span class="no">Floki</span><span class="o">.</span><span class="n">attribute</span><span class="p">(</span><span class="s2">"href"</span><span class="p">)</span>
<span class="o">|></span> <span class="no">List</span><span class="o">.</span><span class="n">first</span><span class="p">()</span>
<span class="k">end</span><span class="p">)</span>
<span class="n">title</span> <span class="o">=</span>
<span class="n">floki_article</span>
<span class="o">|></span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"h1"</span><span class="p">)</span>
<span class="o">|></span> <span class="no">Floki</span><span class="o">.</span><span class="n">text</span><span class="p">()</span>
<span class="n">published</span> <span class="o">=</span>
<span class="n">floki_article</span>
<span class="o">|></span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">".entry-time"</span><span class="p">)</span>
<span class="o">|></span> <span class="no">Floki</span><span class="o">.</span><span class="n">attribute</span><span class="p">(</span><span class="s2">"datetime"</span><span class="p">)</span>
<span class="o">|></span> <span class="no">List</span><span class="o">.</span><span class="n">first</span><span class="p">()</span>
<span class="o">|></span> <span class="no">DateTime</span><span class="o">.</span><span class="n">from_iso8601</span><span class="p">()</span>
<span class="c1"># TODO: this should be a `with` block</span>
<span class="o">|></span> <span class="n">elem</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">episode</span>
<span class="o">|></span> <span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="ss">:floki_article</span><span class="p">,</span> <span class="n">floki_article</span><span class="p">)</span>
<span class="o">|></span> <span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="ss">:article</span><span class="p">,</span> <span class="no">Floki</span><span class="o">.</span><span class="n">text</span><span class="p">(</span><span class="n">floki_article</span><span class="p">,</span> <span class="ss">sep:</span> <span class="s2">" "</span><span class="p">))</span>
<span class="o">|></span> <span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="ss">:links</span><span class="p">,</span> <span class="n">links</span><span class="p">)</span>
<span class="o">|></span> <span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="ss">:title</span><span class="p">,</span> <span class="n">title</span><span class="p">)</span>
<span class="o">|></span> <span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="ss">:published</span><span class="p">,</span> <span class="n">published</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
</code></pre></div>
<!-- livebook:{"output":true} -->
<div class="highlight"><pre class="highlight plaintext"><code>[
%{
article: "1: Getting Things Done by David Allen July 7, 2016 • 1 hour 08 minutes Welcome to Bookworm! To kick things off, we’ve reread a book we both rely on heavily in our lives, Getting Things Done by David Allen. This is the best discussion either of us have had about GTD in a long time. Joe Buhlig Mike Schmitz 33: Analog vs. Digital with Mike Schmitz TPS57: Advanced OmniFocus Setup + Workflow w/ Joe Buhlig Getting Things Done by David Allen Defer Dates in OmniFocus 43 Folders – Merlin Mann SaneBox – Email Management for Any Inbox JIRA Software – Atlassian Confluence – Atlassian The Organized Mind by Daniel J. Levitin Deep Work by Cal Newport Mike's Rating: 4\n Joe's Rating: 5 http://traffic.libsyn.com/bookworm/BW001.mp3 Podcast: Play in new window | Download (Duration: 1:08:39 — 63.0MB) Subscribe: Apple Podcasts | RSS",
floki_article: [
{"article",
[
{"class",
…
]}
],
links: ["http://joebuhlig.com", "http://mikeschmitz.me/", "http://joebuhlig.com/33/",
"http://www.asianefficiency.com/podcast/057-joe-buhlig/",
"https://www.amazon.com/Getting-Things-Done-Stress-Free-Productivity/dp/0143126563/ref=sr_1_1?tag=bookwormfm-20",
"https://discourse.omnigroup.com/t/defer-date-how-to-use/1046", "http://www.43folders.com/",
"http://www.sanebox.com/", "https://www.atlassian.com/software/jira",
"https://www.atlassian.com/software/confluence",
"https://www.amazon.com/Organized-Mind-Thinking-Straight-Information/dp/0147516315/ref=sr_1_1?tag=bookwormfm-20",
"https://www.amazon.com/Deep-Work-Focused-Success-Distracted/dp/1455586692/ref=sr_1_1?tag=bookwormfm-20",
"http://traffic.libsyn.com/bookworm/BW001.mp3", "http://traffic.libsyn.com/bookworm/BW001.mp3",
"http://traffic.libsyn.com/bookworm/BW001.mp3",
"https://itunes.apple.com/us/podcast/bookworm/id1132102092?mt=2&ls=1#episodeGuid=http%3A%2F%2Fbookworm.fm%2F%3Fp%3D12",
"https://bookworm.fm/feed/podcast/"],
request: %Req.Response{
…
},
%{…},
…
]
</code></pre></div>
<p>Everytime I create one of these tiny blocks, it feels wrong to my DRY-trained programming brain, but I know from experience with Livebooks that it rewards little iterative steps.</p>
<p>I've thought of a several different things I want to do with this:</p>
<ul>
<li>a table of "most linked to episodes"</li>
<li>a graph showing all the links</li>
<li>transformation to markdown notes that could be stuck in someone's (<em>cough</em> Mike <em>cough</em>) Obsidian.</li>
</ul>
<p>Let's go!</p>
<h2 id="graphs-and-maps">🗺️ Graphs and Maps</h2>
<p>I thought that the first thing I'm going to need to do is to transform this list of raw bodies into something that we can actually work with and display as a table.</p>
<p>I started to add a Smart Cell Data Explorer, which will automatically add dependencies for all it's plugins and things… but I'm running into it freaking out over the EasyHTML/Floki structs… let's go for the throat and try the graph.</p>
<p>I've done this before for <a href="https://adventofcode.com">Advent of Code</a>, so excuse the magic… 🧙</p>
<div class="highlight"><pre class="highlight elixir"><code><span class="n">graph</span> <span class="o">=</span>
<span class="n">episodes</span>
<span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="no">Graph</span><span class="o">.</span><span class="n">new</span><span class="p">(),</span> <span class="k">fn</span> <span class="p">%{</span><span class="ss">url:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">links:</span> <span class="n">links</span><span class="p">},</span> <span class="n">g</span> <span class="o">-></span>
<span class="n">links</span>
<span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="k">fn</span> <span class="n">link</span><span class="p">,</span> <span class="n">g</span> <span class="o">-></span>
<span class="no">Graph</span><span class="o">.</span><span class="n">add_edge</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">link</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
<span class="n">graph</span> <span class="o">=</span>
<span class="n">episodes</span>
<span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="n">graph</span><span class="p">,</span> <span class="k">fn</span> <span class="p">%{</span><span class="ss">url:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">title:</span> <span class="n">title</span><span class="p">},</span> <span class="n">g</span> <span class="o">-></span>
<span class="no">Graph</span><span class="o">.</span><span class="n">label_vertex</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">title</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
</code></pre></div>
<!-- livebook:{"output":true} -->
<div class="highlight"><pre class="highlight plaintext"><code>#Graph<type: directed, num_vertices: 2201, num_edges: 4844>
</code></pre></div>
<p>Hey presto, we have a Graph object! We can now run lots of algorithms on the graph, see strongly connected components, find backlinks, all that good stuff.</p>
<p>But now we need to display it… the naive way with Graphviz!</p>
<p>LibGraph supplies a <code>Graph.to_dot/1</code> method that graphviz can read:</p>
<div class="highlight"><pre class="highlight elixir"><code><span class="n">dot_output</span> <span class="o">=</span> <span class="n">elem</span><span class="p">(</span><span class="no">Graph</span><span class="o">.</span><span class="n">to_dot</span><span class="p">(</span><span class="n">graph</span><span class="p">),</span> <span class="mi">1</span><span class="p">)</span>
<span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="n">dot_output</span><span class="p">)</span>
</code></pre></div>
<!-- livebook:{"output":true} -->
<div class="highlight"><pre class="highlight plaintext"><code>…
1209938012 -> 3153079849 [weight=1]
1209938012 -> 3305474943 [weight=1]
1209938012 -> 3354501824 [weight=1]
1209938012 -> 3484679997 [weight=1]
…
}
</code></pre></div>
<!-- livebook:{"output":true} -->
<div class="highlight"><pre class="highlight plaintext"><code>:ok
</code></pre></div>
<p>Ordinarily we could paste that into something like <a href="https://dreampuf.github.io/GraphvizOnline">Graphviz Online</a> and we'd be done, but Mike and Joe have linked a <em>lot</em> of links.</p>
<p>Let's output a file for safekeeping.</p>
<div class="highlight"><pre class="highlight elixir"><code><span class="no">File</span><span class="o">.</span><span class="n">write!</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">__DIR__</span><span class="si">}</span><span class="s2">/images/output.dot"</span><span class="p">,</span> <span class="n">dot_output</span><span class="p">)</span>
</code></pre></div>
<!-- livebook:{"output":true} -->
<div class="highlight"><pre class="highlight plaintext"><code>:ok
</code></pre></div>
<p>The <code>output.dot</code> file is understandable by Graphviz tooling:</p>
<div class="highlight"><pre class="highlight elixir"><code><span class="no">System</span><span class="o">.</span><span class="n">shell</span><span class="p">(</span>
<span class="s2">"/opt/homebrew/bin/sfdp -Tpng </span><span class="si">#{</span><span class="n">__DIR__</span><span class="si">}</span><span class="s2">/images/output.dot > </span><span class="si">#{</span><span class="n">__DIR__</span><span class="si">}</span><span class="s2">/images/sfdp.png"</span>
<span class="p">)</span>
</code></pre></div>
<!-- livebook:{"output":true} -->
<div class="highlight"><pre class="highlight plaintext"><code>{"", 0}
</code></pre></div>
<p><img src="/images/articles/2023/08/graph.png" alt=""></p>
<p>This whole bit takes a long time… there's a lot of edges.</p>
<p>I spent some time staring at the initial visualization and realized a few things would need to e straightened up above:</p>
<ul>
<li>[ ] I need to filter out some really common noisy URLs, like libsyn and iTunes.</li>
<li>[X] I needed to get the episode title or name of the book to label those nodes, maybe set them apart visually.</li>
</ul>
<p>Normally I'd just keep working "down" the workbook, but to save on network calls it's easier to get the title when I'm doing the <code>Req.get!</code>.</p>
<div class="highlight"><pre class="highlight elixir"><code><span class="n">filtered_graph</span> <span class="o">=</span>
<span class="n">episodes</span>
<span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="k">fn</span> <span class="p">%{</span><span class="ss">links:</span> <span class="n">links</span><span class="p">}</span> <span class="o">=</span> <span class="n">ep</span> <span class="o">-></span>
<span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span>
<span class="n">ep</span><span class="p">,</span>
<span class="ss">:links</span><span class="p">,</span>
<span class="n">links</span>
<span class="c1"># one domain</span>
<span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="o">&</span><span class="no">String</span><span class="o">.</span><span class="n">match?</span><span class="p">(</span><span class="nv">&1</span><span class="p">,</span> <span class="sr">~r/^https:\/</span><span class="p">\</span><span class="o">/</span><span class="n">bookworm</span><span class="p">\</span><span class="o">.</span><span class="n">fm</span><span class="o">/</span><span class="p">))</span>
<span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
<span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="no">Graph</span><span class="o">.</span><span class="n">new</span><span class="p">(),</span> <span class="k">fn</span> <span class="p">%{</span><span class="ss">url:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">links:</span> <span class="n">links</span><span class="p">,</span> <span class="ss">title:</span> <span class="n">title</span><span class="p">},</span> <span class="n">g</span> <span class="o">-></span>
<span class="n">links</span>
<span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="k">fn</span> <span class="n">link</span><span class="p">,</span> <span class="n">g</span> <span class="o">-></span>
<span class="no">Graph</span><span class="o">.</span><span class="n">add_edge</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">link</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
<span class="o">|></span> <span class="no">Graph</span><span class="o">.</span><span class="n">label_vertex</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">title</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
</code></pre></div>
<p>Let's see if that filtering creates a better graphviz image…</p>
<div class="highlight"><pre class="highlight elixir"><code><span class="no">File</span><span class="o">.</span><span class="n">write!</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">__DIR__</span><span class="si">}</span><span class="s2">/images/output2.dot"</span><span class="p">,</span> <span class="n">elem</span><span class="p">(</span><span class="no">Graph</span><span class="o">.</span><span class="n">to_dot</span><span class="p">(</span><span class="n">filtered_graph</span><span class="p">),</span> <span class="mi">1</span><span class="p">))</span>
<span class="no">System</span><span class="o">.</span><span class="n">shell</span><span class="p">(</span>
<span class="s2">"/opt/homebrew/bin/sfdp -Tpng </span><span class="si">#{</span><span class="n">__DIR__</span><span class="si">}</span><span class="s2">/images/output2.dot > </span><span class="si">#{</span><span class="n">__DIR__</span><span class="si">}</span><span class="s2">/images/filtered.png"</span>
<span class="p">)</span>
</code></pre></div>
<p><img src="/images/articles/2023/08/filtered.png" alt=""></p>
<p>Better… somewhere I had to cut the code for labeling the bookworm vertices, unsure why that broke. Once again, I'm sure I could pass some better options to graphviz to emulate Obsidian… orrrrr…</p>
<h2 id="markdown-for-obsidian">Markdown for Obsidian</h2>
<p>I decided to shift course and try a straightforward and explorable visualization: use <a href="https://obsidian.md">Obsidian</a> to visualize the connections.</p>
<div class="highlight"><pre class="highlight elixir"><code><span class="no">System</span><span class="o">.</span><span class="n">cmd</span><span class="p">(</span><span class="s2">"rm"</span><span class="p">,</span> <span class="p">[</span><span class="s2">"--rf"</span><span class="p">,</span> <span class="s2">"</span><span class="si">#{</span><span class="n">__DIR__</span><span class="si">}</span><span class="s2">/episodes"</span><span class="p">])</span>
<span class="no">System</span><span class="o">.</span><span class="n">cmd</span><span class="p">(</span><span class="s2">"mkdir"</span><span class="p">,</span> <span class="p">[</span><span class="s2">"</span><span class="si">#{</span><span class="n">__DIR__</span><span class="si">}</span><span class="s2">/episodes"</span><span class="p">])</span>
<span class="n">map</span> <span class="o">=</span>
<span class="n">episodes</span>
<span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="k">fn</span> <span class="p">%{</span><span class="ss">url:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">title:</span> <span class="n">title</span><span class="p">}</span> <span class="o">-></span> <span class="p">{</span><span class="n">url</span><span class="p">,</span> <span class="no">String</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="s2">":"</span><span class="p">,</span> <span class="s2">"-"</span><span class="p">)}</span> <span class="k">end</span><span class="p">)</span>
<span class="o">|></span> <span class="no">Map</span><span class="o">.</span><span class="n">new</span><span class="p">()</span>
<span class="n">episodes</span>
<span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="k">fn</span> <span class="p">%{</span><span class="ss">title:</span> <span class="n">title</span><span class="p">,</span> <span class="ss">links:</span> <span class="n">links</span><span class="p">,</span> <span class="ss">article:</span> <span class="n">article</span><span class="p">}</span> <span class="o">-></span>
<span class="n">safe_title</span> <span class="o">=</span> <span class="no">String</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="s2">":"</span><span class="p">,</span> <span class="s2">"-"</span><span class="p">)</span>
<span class="n">content</span> <span class="o">=</span> <span class="sd">"""
# #{title}
#{article}
## Mentions:
#{Enum.reduce(links, '', fn link, str -> "#{str}\n- [[#{Map.get(map, link, link)}]]" end)}
"""</span>
<span class="no">File</span><span class="o">.</span><span class="n">write!</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">__DIR__</span><span class="si">}</span><span class="s2">/episodes/</span><span class="si">#{</span><span class="n">safe_title</span><span class="si">}</span><span class="s2">.md"</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
</code></pre></div>
<!-- livebook:{"output":true} -->
<div class="highlight"><pre class="highlight plaintext"><code>[:ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok,
:ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok,
:ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, …]
</code></pre></div>
<p>This creates a folder called <code>episodes</code> wherever the <code>.livemd</code> file lives. Open that folder as a vault in Obsidian, and you'll get something like this:</p>
<!-- livebook:{"break_markdown":true} -->
<p><img src="/images/articles/2023/08/obsidian_graph.png" alt=""></p>
<!-- livebook:{"break_markdown":true} -->
<p>That's pretty good… but let's set up some data science-y stuff.</p>
<h2 id="data">⚗️ Data</h2>
<p>Let's quickly build a list of <code>Map</code> objects that Livebook's Explorer Dataframes can understand. We'll use a Graph function on the graph object we built earlier in the workbook to find the number of vertices (links) that have an edge to the current episode… essentially how many "backlinks."</p>
<div class="highlight"><pre class="highlight elixir"><code><span class="n">backlinks</span> <span class="o">=</span>
<span class="n">episodes</span>
<span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="k">fn</span> <span class="p">%{</span><span class="ss">title:</span> <span class="n">title</span><span class="p">,</span> <span class="ss">url:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">published:</span> <span class="n">published</span><span class="p">}</span> <span class="o">-></span>
<span class="p">%{</span>
<span class="ss">title:</span> <span class="n">title</span><span class="p">,</span>
<span class="ss">published:</span> <span class="no">DateTime</span><span class="o">.</span><span class="n">to_iso8601</span><span class="p">(</span><span class="n">published</span><span class="p">),</span>
<span class="ss">url:</span> <span class="n">url</span><span class="p">,</span>
<span class="ss">backlinks:</span> <span class="no">Graph</span><span class="o">.</span><span class="n">in_edges</span><span class="p">(</span><span class="n">graph</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span> <span class="o">|></span> <span class="no">Enum</span><span class="o">.</span><span class="n">count</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">end</span><span class="p">)</span>
</code></pre></div>
<!-- livebook:{"output":true} -->
<div class="highlight"><pre class="highlight plaintext"><code>[
%{backlinks: 13, title: "1: Getting Things Done by David Allen", url: "https://bookworm.fm/1/"},
%{
backlinks: 10,
title: "2: The Willpower Instinct by Kelly McGonigal",
url: "https://bookworm.fm/2/"
},
%{backlinks: 7, title: "3: The War Of Art by Steven Pressfield", url: "https://bookworm.fm/3/"},
%{…},
…
]
</code></pre></div>
<!-- livebook:{"attrs":{"assign_to":null,"collect":true,"data_frame":"backlinks","data_frame_alias":"Elixir.Explorer.DataFrame","is_data_frame":false,"lazy":true,"missing_require":"Elixir.Explorer.DataFrame","operations":[{"active":true,"column":null,"data_options":{"backlinks":"integer","title":"string","url":"string"},"datalist":[],"filter":null,"operation_type":"filters","type":"string","value":null}]},"chunks":null,"kind":"Elixir.KinoExplorer.DataTransformCell","livebook_object":"smart_cell"} -->
<div class="highlight"><pre class="highlight elixir"><code><span class="kn">require</span> <span class="no">Explorer</span><span class="o">.</span><span class="no">DataFrame</span>
<span class="n">backlinks</span> <span class="o">|></span> <span class="no">Explorer</span><span class="o">.</span><span class="no">DataFrame</span><span class="o">.</span><span class="n">new</span><span class="p">()</span>
</code></pre></div>
<p><img src="/images/articles/2023/08/Evaluated.png" alt=""></p>
<p>👏👏👏</p>
<p>And there's the answer: they've mentioned (linked to) <em>How To Read a Book</em> 41 times.</p>
<p>Already some interesting insights on there… but there's so much to explore:</p>
<ul>
<li>Could we see this plotted over a timeline?</li>
<li>Do references have a half-life, or do some books ring eternal in Mike and Joe's ears?</li>
<li>How does the episode book score relate to the number of backlinks in the future?</li>
<li>Could we use a transcription to find book mentions that aren't in shownotes?</li>
</ul>
<p>That's for another day…</p>
<p>Check out <a href="https://github.com/evantravers/bookworm-visualization">the repo</a> if you are interested.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://hexdocs.pm/floki/Floki.html">Floki</a></li>
<li><a href="https://hexdocs.pm/libgraph/Graph.html">Graph</a></li>
</ul>