<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"><channel><language>en-us</language><title>Chris Wiegman</title><link>https://chriswiegman.com/</link><atom:link href="https://chriswiegman.com/index.xml" rel="self" type="application/rss+xml"/><atom:link href="https://pubsubhubbub.appspot.com/" rel="hub"/><description>Tech leader, mentor, blogger, and open-source advocate with over 20 years of experience building humane, privacy-focused teams and technology.</description><lastBuildDate>Fri, 21 Aug 2026 08:00:00 -0500</lastBuildDate><sy:updatePeriod>hourly</sy:updatePeriod><sy:updateFrequency>1</sy:updateFrequency><item><title>Enabling URL Support in macOS Terminal.app with tmux</title><enclosure url="https://chriswiegman.com/images/2026/08/my-terminal-with-multiple-panes_hu_9477185e4f3e22b.jpg" length="365268" type="image/jpeg"/><link>https://chriswiegman.com/2026/08/enabling-url-support-in-macos-terminal.app-with-tmux/</link><guid isPermaLink="true">https://chriswiegman.com/2026/08/enabling-url-support-in-macos-terminal.app-with-tmux/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>macOS</category><category>Tools</category><category>Terminal</category><pubDate>Fri, 21 Aug 2026 08:00:00 -0500</pubDate><description>How to restore right-click URL opening in macOS Terminal.app when using tmux for panes.</description><content:encoded><![CDATA[ <p>I&rsquo;ve been using tmux on Mac for a very long time. It&rsquo;s a simple way to get proper panes in Mac&rsquo;s terminal without replacing the app entirely.</p>
<p>Of course there are replacement apps that handle a lot of this out of the box, but from iTerm to Ghostty and others, they always fall short for me in one way or another. So I stick with Terminal, and honestly, it works really well, minus the pane support. tmux fixes that, but it also overrides so much native Terminal functionality that other basic behavior can break, like clicking a URL.</p>
<p>With a little bit of configuration in <code>~/.tmux.conf</code> though, you can restore the ability to open links with a right-click. Below is the config to do just that.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-aconf" data-lang="aconf"><span class="line"><span class="cl"><span class="nb">setw</span> -g mouse <span class="k">on</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> -g word-separators <span class="s2">&#34; \t\n&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">bind</span> -n MouseDown3Pane select-pane -t= \; copy-mode -M \; send-keys -X select-word \; send-keys -X copy-pipe-and-cancel <span class="s2">&#34;sh -c &#39;read -r url; case \&#34;\$url\&#34; in http://*|https://*) open \&#34;\$url\&#34;;; esac&#39;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">bind</span> -T copy-mode-vi    MouseDown3Pane send-keys -X select-word \; send-keys -X copy-pipe-and-cancel <span class="s2">&#34;sh -c &#39;read -r url; case \&#34;\$url\&#34; in http://*|https://*) open \&#34;\$url\&#34;;; esac&#39;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">bind</span> -T copy-mode-emacs MouseDown3Pane send-keys -X select-word \; send-keys -X copy-pipe-and-cancel <span class="s2">&#34;sh -c &#39;read -r url; case \&#34;\$url\&#34; in http://*|https://*) open \&#34;\$url\&#34;;; esac&#39;&#34;</span></span></span></code></pre></div><p>With this snippet you can simply right-click on a link in Terminal and it will open in your default browser, just like it should.</p>
<p>Looking to do more with your tmux config? Take a look at my full <a href="https://github.com/ChrisWiegman/dotfiles/blob/main/shared/configs/.tmux.conf">.tmux.conf</a>. It adds a few other quality-of-life tweaks like pane splitting, vi-style copy mode, and more, all without altering Terminal.app&rsquo;s native look.</p>
 ]]></content:encoded></item><item><title>I'm Still with Apple in 2026... and That's OK</title><enclosure url="https://chriswiegman.com/images/2026/08/im-still-with-apple-in-2026_hu_114c30c5ac4d8ed2.jpg" length="506775" type="image/jpeg"/><link>https://chriswiegman.com/2026/08/im-still-with-apple-in-2026...-and-thats-ok/</link><guid isPermaLink="true">https://chriswiegman.com/2026/08/im-still-with-apple-in-2026...-and-thats-ok/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Hardware</category><category>Tools</category><category>Apple</category><pubDate>Mon, 17 Aug 2026 08:00:00 -0500</pubDate><description>Why I keep coming back to Apple&amp;#39;s ecosystem despite the annual urge to switch, and what actually keeps me there in 2026.</description><content:encoded><![CDATA[ <p>At least once a year I still get the urge to switch up my tech again. In the past I&rsquo;ve gone as small as switching tools and as large as <a href="https://chriswiegman.com/2018/09/from-mac-to-linux-first-impressions-on-a-major-workflow-shift/">switching my entire tech ecosystem</a>. I guess you could say I&rsquo;m always searching for <em>something</em> with my tech and I&rsquo;m OK with that.</p>
<p>These past few weeks have found me looking again at moving off of Apple. I&rsquo;ve been eyeing a new Pixel phone (I still say my Pixel 3XL was probably my all-time favorite phone) as well as Linux laptops. It&rsquo;s tempting, but I&rsquo;m going to stick with Apple.</p>
<h2 id="apple-device-integration-is-still-the-best">Apple device integration is still the best</h2>
<p>Primarily my main reason for sticking with Apple is the device integration. I can start something on one device and pick it up on another and that pretty much works for everything at this point.</p>
<p>Every other tech I&rsquo;ve tried finds some way to limit this and the result is always that for pretty much any real task I have to keep my phone <em>and</em> my laptop with me to get it done. That might seem normal for most, but that&rsquo;s my problem with it. Every one of these devices is a general purpose computer and I want them to behave like it. Apple, these days, does a great job with it.</p>
<h2 id="my-family-is-still-on-apple">My family is still on Apple</h2>
<p>The second reason I stick with Apple, and what brought me back to it in the first place, is that my entire family is &ldquo;all in&rdquo; on Apple. That has come in surprisingly handy these past few years in sharing data, offering tech support and more. Sure, I can (and did) work with them when I was on Android and Linux but the friction was much higher, particularly for my parents and my brother. These days I don&rsquo;t have to guess. I <em>know</em> what they have and we make it work.</p>
<h2 id="everything-i-need-is-actually-available-for-mac">Everything I need is actually available for Mac</h2>
<p>Next, unlike Linux where I spent a large portion of my time fighting to make various services or apps work, everything I need works well on my Apple hardware.</p>
<p>This means I don&rsquo;t have to find weird third-party clients for things like Sonos or deal with alternative apps for many of the things I use on a regular basis. It also means that I can get by on Apple&rsquo;s default apps for almost everything I need. That was simply impossible on Linux.</p>
<p>As a perfect example of this, I wiped my Mac this morning. It took me less than 2 hours to completely restore it, configuration and all. That&rsquo;s not from a backup either. I simply ran my dotfiles&rsquo; setup script and it was all back to working order very quickly. I never could get my Linux machines anywhere near that efficient and it&rsquo;s come in handy on more than one occasion.</p>
<h2 id="iclouds-services-just-work">iCloud&rsquo;s services just work</h2>
<p>Finally there&rsquo;s iCloud. I have a lot of data in it these days and, unlike with Google or the dozens of other services I&rsquo;ve used, I&rsquo;m OK with that. Once I log into iCloud my messages, mail, phone and so much else is just there and works. That has been a very handy thing for me.</p>
<h2 id="apple-really-is-the-best-for-me">Apple really is the best, for me</h2>
<p>So all this, device integration, family, app availability and iCloud put together mean that Apple&rsquo;s ecosystem, while not perfect, is still, in 2026, the right fit for <em>me</em>. Not only do I not see that changing any time soon, but anymore I don&rsquo;t know if I even want it to.</p>
 ]]></content:encoded></item><item><title>I Miss Teaching</title><enclosure url="https://chriswiegman.com/images/2026/08/i-miss-teaching_hu_2859ea01a498295.jpeg" length="259214" type="image/jpeg"/><link>https://chriswiegman.com/2026/08/i-miss-teaching/</link><guid isPermaLink="true">https://chriswiegman.com/2026/08/i-miss-teaching/</guid><dc:creator>Chris Wiegman</dc:creator><category>Personal</category><category>Teaching</category><pubDate>Fri, 14 Aug 2026 08:00:00 -0500</pubDate><description>Reflecting on what I miss about teaching, both in the classroom and in the cockpit as a flight instructor.</description><content:encoded><![CDATA[ <p>It&rsquo;s been a very long time since I&rsquo;ve formally taught in a classroom, almost 13 years, and I miss it.</p>
<p>Since then I&rsquo;ve given plenty of conference talks, Meetup presentations and other more informal teaching, but it&rsquo;s not the same as being in a classroom.</p>
<p>There&rsquo;s something to be said about helping folks move forward in their lives, whether it&rsquo;s a classroom, where I taught computer science, or an airplane cockpit, where I taught folks how to fly. For all the experiences I&rsquo;ve had before or since, it is the teaching that was the most rewarding.</p>
<p>People often ask me why I quit flying for a living. The truth is that the fun part of flying was the teaching. The rest was honestly incredibly boring. Sure, I loved the travel, but that gets old after a while. Considering my first day as a professional pilot, as a flight instructor for SIU, was on September 11, 2001, I can&rsquo;t say being around an airport was ever a fun part of the job. And let&rsquo;s face it, no one really enjoys dealing with air travel today on any level.</p>
<p>What I miss about flying was teaching someone how to fly. More often than not the student is someone who dreamed of flying an airplane most of their lives. They, as the student, sit in the left seat, which is normally the captain&rsquo;s seat on a passenger jet, and I, as the instructor, am in the right, co-pilot seat. Most importantly, from the moment we walk out to the plane to the moment we sign a logbook we both <em>want</em> to be there and we both take something away from it.</p>
<p>If we want to sight see from the plane we can do that. If we want to do more touch-and-gos, we can do that too and each and every turn, landing, takeoff and communication is geared towards making sure that student is that much more ready to meet their goal. When the time comes for their first solo or their checkride I might not be physically with my student, but my heart sure is. Their success is the point, and for every one, my nerves are as bad as theirs, as I can&rsquo;t wait to hear (or see) the results.</p>
<p>In the classroom, it isn&rsquo;t much different. From the first day through the final exam I love working with each and every student, especially at the moment when a complex topic &ldquo;clicks&rdquo; for them.</p>
<p>Whether in the classroom or in the cockpit, though, there&rsquo;s nothing like seeing someone succeed at their own goals and knowing you had a part of that.</p>
<p>When I left flying, being a flight instructor wasn&rsquo;t much of a career. To most of aviation, it is considered &ldquo;time building&rdquo; as the instructors are just there to log hours of their own for their big airline job. That wasn&rsquo;t me. I always thought I would go back to it part-time, but life has never worked out that way.</p>
<p>I&rsquo;m fortunate. I&rsquo;ve been able to teach at the college level in two very different disciplines, aviation and computer science. Today, though, every time &ldquo;back to school&rdquo; is talked about on TV I remember those days and think maybe this will be the year I find myself teaching again. A guy can dream, right?</p>
 ]]></content:encoded></item><item><title>Experimenting with AI: How I use it in 2026</title><enclosure url="https://chriswiegman.com/images/2026/08/how-i-use-ai-in-2026_hu_707e18bc84b6dfdb.jpeg" length="1335857" type="image/jpeg"/><link>https://chriswiegman.com/2026/08/experimenting-with-ai-how-i-use-it-in-2026/</link><guid isPermaLink="true">https://chriswiegman.com/2026/08/experimenting-with-ai-how-i-use-it-in-2026/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>AI</category><category>Tools</category><category>Blogging</category><pubDate>Wed, 12 Aug 2026 08:00:00 -0500</pubDate><description/><content:encoded><![CDATA[ <p>I love writing code.</p>
<p>I&rsquo;ve always loved writing code.</p>
<p>That said, writing code is no longer part of my day job. Today my job is to build my teams and help push our engineering org forward, a very different role that requires a different toolset. In 2026 part of that, whether I like it or not, has involved AI.</p>
<p>To learn <em>how</em> to use AI I&rsquo;ve also deployed it in some of my own work, but that use case has been far more limited and, frankly, not something I&rsquo;ve found valuable or even particularly useful.</p>
<h1 id="where-ai-has-helped-me">Where AI has helped me</h1>
<p>To claim AI, as a tech, hasn&rsquo;t been helpful at work would be a lie. The simple fact is that my workload is such that I simply couldn&rsquo;t get it all done without AI (trust me, I&rsquo;ve tried). For the most part where I use it is in a few areas:</p>
<p>First is meeting notes and transcriptions. My company pays for Fellow.ai but I&rsquo;ve stuck with the standard Google Meet notes and transcriptions for nearly all my calls, often 10 or even more per day. Without it I couldn&rsquo;t keep up. It records all the important parts and sends them to my email where I automatically filter them into a &ldquo;Meetings&rdquo; label for processing later. This has saved my butt so many times as I change context far too often during a given day to otherwise be able to keep up with everything.</p>
<p>Next is searching and reporting on internal information. Work data is in a few services including Notion, Google Workspace, Jira and GitHub. When I need to put these together I can spend hours I don&rsquo;t have outside of meetings or I can ask our work AI, Claude, to find me what I need. I still proofread for accuracy, but it&rsquo;s been very helpful in putting together what could otherwise require me spending hours going through dozens, or more, of pages and ideas that are rarely organized in any usable way.</p>
<p>Finally is drafting and editing. For work docs I&rsquo;ll often use AI in one of two ways with documents. Either I ask it to draft me something or I ask it to stitch together something coherent from pages of notes and other info (it&rsquo;s surprisingly hard to write a document of any kind in the fits and starts that come with my meeting schedule). For the most part the editing is helpful. The drafts, well, I will often keep the structure but, if I have time, I find myself rewriting most of the rest of it to make sure it is accurate.</p>
<p>In my personal life I avoid AI for writing but have also found it very helpful in one-off or other simple tasks to help me with this site and other projects. Over the spring I used it heavily to help me implement small scripts that I just didn&rsquo;t have time for otherwise. They&rsquo;ve been so helpful to my blogging workflow that I&rsquo;ve even published many of those as either npm scripts or as VS Code extensions (where appropriate).</p>
<p>Taken together I&rsquo;ve found AI helpful in tasks I can verify and that I wouldn&rsquo;t have time to do myself otherwise.</p>
<h1 id="where-i-still-wont-use-ai">Where I still won&rsquo;t use AI</h1>
<p>For as helpful as AI has been, there are areas I&rsquo;ve found where it is better to avoid it.</p>
<p>The first of these is as a replacement of my search engine or really for searching for any facts at all (outside of my verifiable work data). AI is a statistical machine that predicts the next token in a pattern. It doesn&rsquo;t, and can&rsquo;t, understand if what it is generating is factually correct and it really should never be used as such.</p>
<p>Second is writing here. It&rsquo;s helped me with some scripts but I absolutely will not use it to write. This blog is my voice and no one else&rsquo;s. I&rsquo;m not throwing that away to a machine.</p>
<p>Finally it is in drafting documents or code on anything I cannot verify. If I don&rsquo;t know the material well I&rsquo;ll do it myself. It&rsquo;s far safer than suffering the embarrassment of submitting something that sounds good but is factually wrong. I see it all around me too, people taking shortcuts and trusting the AI to &ldquo;do it for them.&rdquo; It doesn&rsquo;t take long to spot the errors in this approach and I won&rsquo;t do that to others.</p>
<h1 id="where-i-still-hate-ai">Where I still hate AI</h1>
<p>I use AI because it is an expected, and indeed required, tool for my work in 2026. As ending my career isn&rsquo;t an option I will continue to do so where it makes sense.</p>
<p>That said, I hate just how ridiculous the power behind AI is and what that power is doing to the world around us. Given that, I&rsquo;ve experimented with local models and alternatives to the popular models and here I&rsquo;m starting to find some helpful utility in the same use cases where I&rsquo;ve found Claude helpful.</p>
<p>In the end, AI, like any tech, has its utility. Where it falls apart today is in the companies and politics behind it. Given that, I&rsquo;ll use what I have to where I have to and continue to implement &ldquo;better&rdquo; solutions into my own workflow as much as I can.</p>
 ]]></content:encoded></item><item><title>Why Resetting my Music Library was the Right Move</title><enclosure url="https://chriswiegman.com/images/2026/08/starting-my-music-library-over-again_hu_cbd2f626727c8842.jpg" length="799759" type="image/jpeg"/><link>https://chriswiegman.com/2026/08/why-resetting-my-music-library-was-the-right-move/</link><guid isPermaLink="true">https://chriswiegman.com/2026/08/why-resetting-my-music-library-was-the-right-move/</guid><dc:creator>Chris Wiegman</dc:creator><category>Personal</category><category>Music</category><category>Digital Life</category><category>Minimalism</category><pubDate>Mon, 10 Aug 2026 08:00:00 -0500</pubDate><description>Why I wiped a two-thousand-album music library this year and what I&amp;#39;m building in its place.</description><content:encoded><![CDATA[ <p>I&rsquo;ve written before about how I <a href="https://chriswiegman.com/2026/03/a-digital-reset/">reset my digital life</a> every so often. Most of those resets are housekeeping: old posts deleted, histories cleared, accounts closed and obsolete email/messages/etc cleared out. This time I&rsquo;ve taken it a lot further and the reset that actually mattered to me most was my music library.</p>
<h2 id="why-i-wiped-it">Why I Wiped It</h2>
<p>I wasn&rsquo;t unhappy with my library because it was messy. I was unhappy because I was bored with it. I&rsquo;d re-listened to the same collection for years, adding albums, but never really pruning anything, and at some point I stopped listening to anything new. I kept reaching for the same few hundred songs out of thousands, not because I wanted to hear them, but because they were familiar.</p>
<p>By the time I reset it in March, the library had grown to more than 2,000 albums. That&rsquo;s not a small thing to walk away from, but the size was part of the problem. Somewhere in there was music I actually loved, buried under years of half-remembered additions, one-off recommendations I never revisited and albums I only kept out of habit.</p>
<h2 id="starting-fresh-again">Starting Fresh: Again</h2>
<p>So I erased it. All of it. No &ldquo;just in case&rdquo; folder set aside for later.</p>
<p>Then, since March, I&rsquo;ve added back more than 1,000 albums, and I&rsquo;ve realized I&rsquo;m just rebuilding the old library from memory.</p>
<p>So I&rsquo;ve deleted it again and this time I&rsquo;m building a new one around what I actually want to listen to, which turns out to be narrower and more specific than &ldquo;everything I&rsquo;ve ever liked.&rdquo;</p>
<h2 id="what-im-actually-looking-for">What I&rsquo;m Actually Looking For</h2>
<p>The goal this time isn&rsquo;t volume. It&rsquo;s peace. I want music that calms me rather than music that&rsquo;s simply familiar. That means spending more time with a smaller set of artists and genres instead of skimming across everything and being a lot more willing to leave an album out if it doesn&rsquo;t do anything for me.</p>
<p>It also means listening more intentionally. Instead of putting the library on shuffle and letting habit take over I&rsquo;m choosing what goes in and, just as often, choosing what stays out.</p>
<h2 id="where-this-goes">Where This Goes</h2>
<p>I don&rsquo;t know if this library will end up at 2,000+ albums again, or even if it ever should. That&rsquo;s sort of the point. The old one grew by accumulation. This one is growing by choice, and so far that&rsquo;s made all the difference.</p>
 ]]></content:encoded></item><item><title>My Hugo Theme Continues to Evolve</title><enclosure url="https://chriswiegman.com/images/2026/08/screenshot-2026-08-04-at-19.56.11_hu_8dcffd669cdb8c17.jpg" length="376748" type="image/jpeg"/><link>https://chriswiegman.com/2026/08/my-hugo-theme-continues-to-evolve/</link><guid isPermaLink="true">https://chriswiegman.com/2026/08/my-hugo-theme-continues-to-evolve/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Web Development</category><category>Open Source</category><category>Hugo</category><pubDate>Fri, 07 Aug 2026 08:00:00 -0500</pubDate><description>A high-level look back at what&amp;#39;s changed in my Hugo theme, Kana, since I moved this site back to Hugo late last year.</description><content:encoded><![CDATA[ <p>Last fall I <a href="https://chriswiegman.com/2025/12/a-hopefully-final-move-back-to-hugo/">moved this site back to Hugo</a> and resumed work on <a href="https://github.com/ChrisWiegman/kana-hugo-theme">its theme</a>. I haven&rsquo;t written much about the theme itself since then, so it feels like a good time for an overview of what&rsquo;s actually changed under the hood.</p>
<p>None of this was one big redesign. It&rsquo;s been a lot of small releases, sometimes a few in a single day. Each one nudged things forward, but looking back at the changelog, a few themes stand out.</p>
<h2 id="a-new-coat-of-paint">A New Coat of Paint</h2>
<p>The most visible change was a full color refresh back in the spring. It was intentionally a breaking change: I wanted an excuse to actually sit down and rethink the palette instead of tweaking it forever. It&rsquo;s more &ldquo;earthy&rdquo; than my previous palette and just feels more like me. Everything since then has been built on top of that, and I love it so much that I&rsquo;ve turned it into a <a href="https://marketplace.visualstudio.com/items?itemName=ChrisWiegman.kana-vscode-theme">VS Code theme</a>, an <a href="https://kana.sh/xcode/">Xcode theme</a>, and <a href="https://kana.sh/">more</a>.</p>
<h2 id="ive-added-a-library">I&rsquo;ve Added a Library</h2>
<p>The <a href="https://chriswiegman.com/library/">library</a> has probably gotten more attention than any other part of the theme this year. It started with the goal of backing up my Goodreads history and has since grown to include filtering by year, author, and rating, along with summary cards for years and authors. Along the way I fixed a few bugs and tweaked the design here and there and now I&rsquo;m pretty darn happy with it.</p>
<p>Under the hood I also added ISBN, ASIN and cover image support to book front matter, which finally made the covers reliable instead of a guess (even though I don&rsquo;t display them yet).</p>
<p>When I launched it, I displayed all the books at once, which didn&rsquo;t make much sense. I later added manual pagination, but as of this week, the list loads progressively as you scroll, which feels a lot more natural on a page like this.</p>
<p>Alongside that change, I refactored the underlying script to make it simpler and faster.</p>
<h2 id="faster-leaner-pages">Faster, Leaner Pages</h2>
<p>A lot of the less visible work went into performance. Content images now get a proper WebP srcset through a render hook, load lazily when they&rsquo;re offscreen, and let the browser select an appropriately sized image instead of always downloading the largest one. Header images received the same treatment, and the search index is now truncated so its payload doesn&rsquo;t balloon on larger sites.</p>
<p>I also cleaned up some things that were quietly working against performance and correctness, like a <code>fetchpriority=high</code> hint that had leaked onto content images instead of staying reserved for actual LCP elements.</p>
<h2 id="cleaner-feeds-and-metadata">Cleaner Feeds and Metadata</h2>
<p>RSS took a few passes. Feeds broke and got fixed for newer Hugo releases, notes finally got their own working RSS feed, and the main feed&rsquo;s <code>dc:creator</code> field is now configurable through site parameters instead of being hardcoded. I also replaced the old <code>schema.json</code> approach with a proper JSON-LD header, and standardized how page and feed titles are formatted so they&rsquo;re consistent everywhere instead of ad hoc.</p>
<h2 id="small-stuff-that-adds-up">Small Stuff That Adds Up</h2>
<p>The rest was a lot of polish: a skip-to-content link, a noscript fallback for search, <code>datetime</code> attributes on post timestamps so the semantics are actually correct, the monospace font was finally removed from blockquotes so quotes stop looking like code, and a handful of favicon fixes (Safari really did not like the old one).</p>
<h2 id="a-better-development-workflow">A Better Development Workflow</h2>
<p>None of this would have been as easy to keep shipping without the boring maintenance work alongside it. I added ESLint and switched to Hugo&rsquo;s own <code>gotmplfmt</code> formatter for templates, migrated the dev site config from <code>hugo.json</code> to <code>hugo.toml</code>, and kept dependencies current with pretty much every release. There were also a couple of straight-up refactors just to cut cruft out of the codebase, with no user-facing change at all.</p>
<h2 id="im-loving-this-work">I&rsquo;m Loving This Work</h2>
<p>What strikes me looking back over all of this is just how steady the pace has been. No big rewrites, mostly a lot of small, deliberate improvements. That&rsquo;s exactly the kind of work I want to be doing with this site right now, and I&rsquo;m looking forward to seeing what the next few months of changes look like.</p>
<p>As a bonus, I&rsquo;ve made this all available for your site at <a href="https://kana.sh/">kana.sh</a></p>
 ]]></content:encoded></item><item><title>Why I Still Bother with RSS in 2026</title><enclosure url="https://chriswiegman.com/images/2026/08/screenshot-2026-08-04-at-18.02.00_hu_f9775e90b220dd69.jpg" length="444158" type="image/jpeg"/><link>https://chriswiegman.com/2026/08/why-i-still-bother-with-rss-in-2026/</link><guid isPermaLink="true">https://chriswiegman.com/2026/08/why-i-still-bother-with-rss-in-2026/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Tools</category><category>Blogging</category><pubDate>Wed, 05 Aug 2026 08:00:00 -0500</pubDate><description>Why I switched RSS readers again, and why RSS, not social media algorithms, is still the best way to keep up with the sites that actually matter to me in 2026.</description><content:encoded><![CDATA[ <p>I&rsquo;ve recently changed RSS tools again. It wasn&rsquo;t because my old setup stopped working, but because my RSS client of choice, <a href="https://reeder.app">Reeder</a>, has pivoted to be, well, something else. There&rsquo;s still Reeder Classic but <a href="https://gloria.social/@rizzi/116709571555459881">it&rsquo;s not being well maintained at this point</a>. It was time to move on.</p>
<p>After a lot of trial and error I settled on <a href="https://www.goldenhillsoftware.com/unread/">Unread</a> as my feed reader. It works well, though I still prefer Reeder back in its prime. Unread does have one feature I haven&rsquo;t seen in years, though: a free syncing service that not only works, but works well. It works so well, in fact, that I decided it was time to drop <a href="https://feedbin.com">Feedbin</a>, which had been syncing Reeder for me for the last few years. It&rsquo;s been a great combination.</p>
<p>When I switched I also decided it was time to start my feed subscriptions over, since it had been a while and I was subscribing to literally hundreds of feeds, most of which I no longer found relevant.</p>
<p>Today I&rsquo;ve got a few dozen feeds in Unread, and it has reminded me of just how much I appreciate RSS feeds, and why I bothered with all this switching in the first place.</p>
<p>Sure, I could get my news through social media, wading through AI slop and rage bait to find it, but why?</p>
<p>The truth is I don&rsquo;t need 1,000 sources, and I definitely don&rsquo;t need to find out what&rsquo;s trending for everyone else (or even anyone at all). What I don&rsquo;t want is to get sucked into rabbit holes of topics I don&rsquo;t care about.</p>
<p>What I need is to keep up with the few sites that are important to me and RSS is still the best tech to do that.</p>
<p>Thankfully, most content management systems, including WordPress and Hugo, publish their content in RSS format, and it&rsquo;s easy for readers to find the feed (at least in most cases). All that&rsquo;s left is to find the reader that works for you and paste in the URL of the site you want to subscribe to.</p>
<p>So if you&rsquo;re stuck in algorithmic hell, tired of the endless scrolling, the AI slop and the rage bait, dust off an RSS client and add the sites you care about. If you&rsquo;re like me it might just restore your faith in the web.</p>
 ]]></content:encoded></item><item><title>Battling Link Rot on a Hugo Site</title><enclosure url="https://chriswiegman.com/images/2026/08/battling-link-rot-on-a-hugo-site_hu_f527b5324a8d92ec.jpg" length="424380" type="image/jpeg"/><link>https://chriswiegman.com/2026/08/battling-link-rot-on-a-hugo-site/</link><guid isPermaLink="true">https://chriswiegman.com/2026/08/battling-link-rot-on-a-hugo-site/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Blogging</category><category>Web Development</category><category>Tools</category><category>Hugo</category><pubDate>Mon, 03 Aug 2026 08:00:00 -0500</pubDate><description>Using Lychee both locally and on GitHub to stay on top of link rot on a Hugo site.</description><content:encoded><![CDATA[ <p>One of the hardest parts of keeping this site running as it ages has been staying on top of <a href="https://en.wikipedia.org/wiki/Link_rot">link rot</a>. Having 18 years of links adds up, especially when you’re talking about tech that counts in days what the rest of the world counts in years.</p>
<p>For a number of years I used <a href="https://www.drlinkcheck.com">Dr. Link Check</a> to give me a report of broken links, which I’d then fix by hand. It’s been effective but this site has grown to a point where I need an ever-more-expensive plan to keep up with it all. That’s OK, but not ideal.</p>
<p>Since moving back to <a href="https://chriswiegman.com/2025/12/a-hopefully-final-move-back-to-hugo/">Hugo</a>, whose links are much easier to parse programmatically, I thought there had to be a better way.</p>
<p>There is, and it’s called <a href="https://lychee.cli.rs">Lychee</a>.</p>
<p>Lychee is a Rust CLI utility that checks links programmatically and generates a report I can use to fix them. With Dr. Link Check I ran link checks once a year when I paid for a month of the service. With Lychee I run link checks automatically once a month or on demand, either as a GitHub Action or on my laptop. This means I can stay ahead of the inevitable link rot over time and make sure this site continues to function as I intended.</p>
<h2 id="the-lychee-configuration">The Lychee Configuration</h2>
<p>Regardless of where I run Lychee, locally or in the GitHub Action, it uses the same configuration file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># Shared config for `make check-links` and .github/workflows/link-check.yml.</span>
</span></span><span class="line"><span class="cl"><span class="c"># lychee loads this automatically from the working directory.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">cache</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="nx">max_cache_age</span> <span class="p">=</span> <span class="s2">&#34;25d&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">no_progress</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Accept 403s: many sites block bots outright, which isn&#39;t a real broken link.</span>
</span></span><span class="line"><span class="cl"><span class="nx">accept</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;200..=299&#34;</span><span class="p">,</span> <span class="s2">&#34;403&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">exclude</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;^(?:/|#|\.)&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;^file:&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;^mailto:&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;https?://(www\.)?chriswiegman\.com&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">exclude_path</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;public/library/&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Identify ourselves so hosts don&#39;t silently throttle/block an anonymous bot UA.</span>
</span></span><span class="line"><span class="cl"><span class="nx">user_agent</span> <span class="p">=</span> <span class="s2">&#34;Mozilla/5.0 (compatible; chriswiegman.com link checker; +https://chriswiegman.com)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Give slower sites more time before giving up, and retry transient/rate-limit</span>
</span></span><span class="line"><span class="cl"><span class="c"># failures with a real gap instead of hammering them again immediately.</span>
</span></span><span class="line"><span class="cl"><span class="nx">timeout</span> <span class="p">=</span> <span class="mi">30</span>
</span></span><span class="line"><span class="cl"><span class="nx">max_retries</span> <span class="p">=</span> <span class="mi">4</span>
</span></span><span class="line"><span class="cl"><span class="nx">retry_wait_time</span> <span class="p">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Keep overall load low enough that we don&#39;t trip rate limits or saturate</span>
</span></span><span class="line"><span class="cl"><span class="c"># our own connection pool (defaults are 128 total / 10 per host / 50ms gap).</span>
</span></span><span class="line"><span class="cl"><span class="nx">max_concurrency</span> <span class="p">=</span> <span class="mi">32</span>
</span></span><span class="line"><span class="cl"><span class="nx">host_concurrency</span> <span class="p">=</span> <span class="mi">4</span>
</span></span><span class="line"><span class="cl"><span class="nx">host_request_interval</span> <span class="p">=</span> <span class="s2">&#34;500ms&#34;</span></span></span></code></pre></div><h2 id="running-lychee-locally">Running Lychee Locally</h2>
<p>To run Lychee locally I use a <a href="https://www.gnu.org/software/make/">Make</a> target. This generates a fresh report each time I run it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Makefile" data-lang="Makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">check</span>-<span class="n">links</span>
</span></span><span class="line"><span class="cl"><span class="nf">check-links</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	rm -f lychee-report.md
</span></span><span class="line"><span class="cl">	@command -v lychee &gt;/dev/null 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="o">||</span> <span class="o">{</span> <span class="nb">echo</span> <span class="s2">&#34;lychee not found. Install it with: brew install lychee&#34;</span><span class="p">;</span> <span class="nb">exit</span> 1<span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl">	hugo --gc --minify
</span></span><span class="line"><span class="cl">	@lychee -vv --root-dir <span class="s1">&#39;$(CURDIR)/public&#39;</span> --format markdown --output lychee-report.md <span class="s1">&#39;./public/**/*.html&#39;</span><span class="p">;</span>
</span></span></code></pre></div><p>With this I only run <code>make check-links</code> from my terminal and it generates a Markdown report I can use to clean up any bad links.</p>
<h2 id="running-as-a-github-action">Running as a GitHub Action</h2>
<p>I also use the following GitHub Action to check the links on my site monthly and create an issue with any bad links found:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Check for Broken Links</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schedule</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Run monthly at 7 AM UTC on the 1st of every month</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;0 7 1 * *&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">permissions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="l">read</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">issues</span><span class="p">:</span><span class="w"> </span><span class="l">write</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">concurrency</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="l">${{ github.workflow }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cancel-in-progress</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">check-links</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Checkout</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v7</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">submodules</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">fetch-depth</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup Hugo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-hugo@v3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">hugo-version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;latest&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">extended</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Hugo&#39;s resources/ (processed images, bundles) and its module/build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># cache (.hugo_cache) are cheap to share across runs but expensive to</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># rebuild from scratch, so both are restored from one cache entry</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># keyed on everything that can change the build output.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Hugo build artifacts</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            resources
</span></span></span><span class="line"><span class="cl"><span class="sd">            .hugo_cache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.os }}-hugo-${{ hashFiles(&#39;**/go.mod&#39;, &#39;**/go.sum&#39;, &#39;**/package-lock.json&#39;, &#39;**/pnpm-lock.yaml&#39;, &#39;**/yarn.lock&#39;, &#39;hugo.toml&#39;, &#39;hugo.yaml&#39;, &#39;hugo.json&#39;, &#39;config/**&#39;, &#39;assets/**&#39;) }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            ${{ runner.os }}-hugo-</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build site</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">shell</span><span class="p">:</span><span class="w"> </span><span class="l">bash</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">hugo --gc --minify --cacheDir &#34;${{ github.workspace }}/.hugo_cache&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Lychee output</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">.lycheecache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">cache-lychee-${{ hashFiles(&#39;.lycheecache&#39;) }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="l">cache-lychee-</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Check for broken links</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">lychee</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">lycheeverse/lychee-action@v2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">args</span><span class="p">:</span><span class="w"> </span>--<span class="l">root-dir &#39;${{ github.workspace }}/public&#39; &#39;./public/**/*.html&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">fail</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="l">markdown</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">jobSummary</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{secrets.GITHUB_TOKEN}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload lychee report</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">always()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-artifact@v7</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">lychee-report</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">./lychee/out.md</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">if-no-files-found</span><span class="p">:</span><span class="w"> </span><span class="l">warn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create or update issue with broken links report</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">steps.lychee.outputs.exit_code == &#39;1&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/github-script@v9</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">script</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            const fs = require(&#39;fs&#39;);
</span></span></span><span class="line"><span class="cl"><span class="sd">            const maxBodyBytes = 60000;
</span></span></span><span class="line"><span class="cl"><span class="sd">            const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
</span></span></span><span class="line"><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            let report = &#39;&#39;;
</span></span></span><span class="line"><span class="cl"><span class="sd">            try {
</span></span></span><span class="line"><span class="cl"><span class="sd">              const lycheeOutput = fs.readFileSync(&#39;./lychee/out.md&#39;, &#39;utf8&#39;);
</span></span></span><span class="line"><span class="cl"><span class="sd">              const outputBytes = Buffer.byteLength(lycheeOutput, &#39;utf8&#39;);
</span></span></span><span class="line"><span class="cl"><span class="sd">              if (outputBytes &gt; maxBodyBytes) {
</span></span></span><span class="line"><span class="cl"><span class="sd">                const trimmed = lycheeOutput.slice(0, Math.floor(maxBodyBytes * 0.9));
</span></span></span><span class="line"><span class="cl"><span class="sd">                report = `${trimmed}\n\n...output truncated. [See full report in workflow logs](${runUrl}).`;
</span></span></span><span class="line"><span class="cl"><span class="sd">              } else {
</span></span></span><span class="line"><span class="cl"><span class="sd">                report = lycheeOutput;
</span></span></span><span class="line"><span class="cl"><span class="sd">              }
</span></span></span><span class="line"><span class="cl"><span class="sd">            } catch (error) {
</span></span></span><span class="line"><span class="cl"><span class="sd">              report = `Could not read lychee output. [See workflow logs](${runUrl}) for details.`;
</span></span></span><span class="line"><span class="cl"><span class="sd">            }
</span></span></span><span class="line"><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            const body = `## Broken Links Report\n\n_Generated by weekly link check — [workflow run](${runUrl})_\n\n${report}`;
</span></span></span><span class="line"><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            const issues = await github.rest.issues.listForRepo({
</span></span></span><span class="line"><span class="cl"><span class="sd">              owner: context.repo.owner,
</span></span></span><span class="line"><span class="cl"><span class="sd">              repo: context.repo.repo,
</span></span></span><span class="line"><span class="cl"><span class="sd">              state: &#39;open&#39;,
</span></span></span><span class="line"><span class="cl"><span class="sd">              labels: [&#39;broken-links&#39;]
</span></span></span><span class="line"><span class="cl"><span class="sd">            });
</span></span></span><span class="line"><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            if (issues.data.length === 0) {
</span></span></span><span class="line"><span class="cl"><span class="sd">              await github.rest.issues.create({
</span></span></span><span class="line"><span class="cl"><span class="sd">                owner: context.repo.owner,
</span></span></span><span class="line"><span class="cl"><span class="sd">                repo: context.repo.repo,
</span></span></span><span class="line"><span class="cl"><span class="sd">                title: &#39;Broken Links Detected&#39;,
</span></span></span><span class="line"><span class="cl"><span class="sd">                body: body,
</span></span></span><span class="line"><span class="cl"><span class="sd">                labels: [&#39;broken-links&#39;, &#39;bug&#39;]
</span></span></span><span class="line"><span class="cl"><span class="sd">              });
</span></span></span><span class="line"><span class="cl"><span class="sd">            } else {
</span></span></span><span class="line"><span class="cl"><span class="sd">              await github.rest.issues.createComment({
</span></span></span><span class="line"><span class="cl"><span class="sd">                owner: context.repo.owner,
</span></span></span><span class="line"><span class="cl"><span class="sd">                repo: context.repo.repo,
</span></span></span><span class="line"><span class="cl"><span class="sd">                issue_number: issues.data[0].number,
</span></span></span><span class="line"><span class="cl"><span class="sd">                body: body
</span></span></span><span class="line"><span class="cl"><span class="sd">              });
</span></span></span><span class="line"><span class="cl"><span class="sd">            }</span></span></span></code></pre></div><p>Upload this to your <code>.github/workflows</code> folder in your repo and it will handle everything except actually fixing the links.</p>
<p>Taken together, the config and these run methods are helping me clean up this site and keep it running well going forward. If you run a Hugo site of your own, both the config and the workflow above should drop in with only a little modification.</p>
 ]]></content:encoded></item><item><title>You Don't Need the DuckDuckGo noai Subdomain</title><enclosure url="https://chriswiegman.com/images/2026/06/you-dont-need-the-duckduckgo-noai-subdomain_hu_a64b1feeddba5047.jpg" length="553262" type="image/jpeg"/><link>https://chriswiegman.com/2026/06/you-dont-need-the-duckduckgo-noai-subdomain/</link><guid isPermaLink="true">https://chriswiegman.com/2026/06/you-dont-need-the-duckduckgo-noai-subdomain/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Tools</category><category>Software</category><category>Privacy</category><category>Digital Life</category><pubDate>Mon, 08 Jun 2026 08:00:00 -0500</pubDate><description/><content:encoded><![CDATA[ <p>There&rsquo;s a lot of hype about accessing <a href="https://duckduckgo.com">DuckDuckGo</a> via its &ldquo;noai&rdquo; subdomain. It works, but there&rsquo;s a better way.</p>
<p>Instead of using a special domain to access DuckDuckGo, a method that works, but not in every situation, you can just use the normal DuckDuckGo domain, without AI and customizable even further to serve your needs. Just look at the screenshot at the top of this post. It has no AI, custom colors and a lot of other settings customized. Best of all, DuckDuckGo shows up like this on all of my devices, in Safari or other browsers, without plugins or anything else.</p>
<h1 id="edit-the-duckduckgo-settings">Edit the DuckDuckGo Settings</h1>
<p>If you click the menu in the top right corner of DuckDuckGo you can select &ldquo;Settings.&rdquo;</p>
<figure>
    <img class="with-caption" width="3412" height="560" src="https://chriswiegman.com/images/2026/06/find-the-duckduckgo-settings-menu.png" alt="Settings for DuckDuckGo can be found under the menu" decoding="async" loading="lazy" srcset="https://chriswiegman.com/images/2026/06/find-the-duckduckgo-settings-menu_hu_4f50cee075531be.webp 850w,https://chriswiegman.com/images/2026/06/find-the-duckduckgo-settings-menu_hu_85d1db19e06e0705.webp 710w,https://chriswiegman.com/images/2026/06/find-the-duckduckgo-settings-menu_hu_c5324699e53929ec.webp 300w" sizes="(max-width: 300px) 300w, (max-width: 710px) 710w, 850w" /><figcaption>Settings for DuckDuckGo can be found under the menu</figcaption></figure><p>Here you can set colors, privacy settings and a lot more&hellip; including AI settings.</p>
<figure>
    <img class="with-caption" width="1860" height="1096" src="https://chriswiegman.com/images/2026/06/duckduckgo-ai-settings.png" alt="The DuckDuckGo AI Settings" decoding="async" loading="lazy" srcset="https://chriswiegman.com/images/2026/06/duckduckgo-ai-settings_hu_8f9db642d24f1830.webp 850w,https://chriswiegman.com/images/2026/06/duckduckgo-ai-settings_hu_299b60f2d4d8bbb0.webp 710w,https://chriswiegman.com/images/2026/06/duckduckgo-ai-settings_hu_3fdd6e5160effbed.webp 300w" sizes="(max-width: 300px) 300w, (max-width: 710px) 710w, 850w" /><figcaption>The DuckDuckGo AI Settings</figcaption></figure><h1 id="save-your-settings">Save your Settings</h1>
<p>When you have DuckDuckGo configured the way you want it, simply save your settings with the save dialogue. I would recommend setting a password with your password manager so it&rsquo;s sufficiently complex enough to avoid being guessed.</p>
<figure>
    <img class="with-caption" width="1030" height="680" src="https://chriswiegman.com/images/2026/06/duckduckgo-manage-save.png" alt="The DuckDuckGo settings sections for saving and loading your settings" decoding="async" loading="lazy" srcset="https://chriswiegman.com/images/2026/06/duckduckgo-manage-save_hu_a39a0a4c7fc00e64.webp 850w,https://chriswiegman.com/images/2026/06/duckduckgo-manage-save_hu_2ec3d7174a3e8233.webp 710w,https://chriswiegman.com/images/2026/06/duckduckgo-manage-save_hu_3a17bb0ab76c3a58.webp 300w" sizes="(max-width: 300px) 300w, (max-width: 710px) 710w, 850w" /><figcaption>The DuckDuckGo settings sections for saving and loading your settings</figcaption></figure><p>Note that DuckDuckGo saves passwords anonymously. This means that you&rsquo;re never identified and you don&rsquo;t even tell them who you are. There&rsquo;s no email or other identifying info, just the password.</p>
<h1 id="load-the-settings-on-other-devices">Load the Settings on Other Devices</h1>
<p>After you&rsquo;ve saved your settings on one device or browser, simply open DuckDuckGo on another device or browser, go to the settings and load them with the password you used.</p>
<p>That&rsquo;s it.</p>
<p>With about 5 minutes of work you can not only turn off AI in DuckDuckGo, but you can also customize its design and other features to better suit the way you work.</p>
 ]]></content:encoded></item><item><title>Backing Up All My GitHub Code</title><enclosure url="https://chriswiegman.com/images/2026/05/backup-github_hu_5437465fae715729.jpg" length="498541" type="image/jpeg"/><link>https://chriswiegman.com/2026/05/backing-up-all-my-github-code/</link><guid isPermaLink="true">https://chriswiegman.com/2026/05/backing-up-all-my-github-code/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Open Source</category><category>Development</category><category>Digital Life</category><category>Software</category><category>Tools</category><pubDate>Mon, 04 May 2026 08:00:00 -0500</pubDate><description>A simple command line tool for backing up all your public and private GitHub repos.</description><content:encoded><![CDATA[ <p>I&rsquo;ve hit the point where I don&rsquo;t trust GitHub but, for right now, it&rsquo;s still my best option for the projects I host. That means I need a simple and reliable solution to back up my code there.</p>
<p>To address this I&rsquo;ve created a simple <a href="https://github.com/ChrisWiegman/backup-github">Backup GitHub</a> app that does one thing and one thing well: it fully backs up all my GitHub repos, public and private.</p>
<p>This script, with few options, has you log into your GitHub account without playing games with access tokens and stores that login in your Mac keychain. It then simply backs up every repo in your account to the current directory (though you can specify another directory with a flag, if you want) and it does it all super fast.</p>
<p>That&rsquo;s it. No fancy bells or whistles&hellip; just a simple backup of what matters most: all your GitHub code.</p>
<p>You can install it from Homebrew or directly from GitHub and then just run it from the folder you want to backup your repos to. If you run it a second time it will simply update the repos it finds making it perfect for keeping your backups current over time.</p>
<p>As a bonus, this was a fun little project for me. I have been experimenting with how to use AI, but not for this. The app itself was done by hand over the last few days for fun and, fun it was. I don&rsquo;t have many opportunities for that lately.</p>
<p>So if you need a quick and easy way to backup your GitHub code, <a href="https://github.com/ChrisWiegman/backup-github">give it a shot</a>.</p>
<p>One note&hellip; it&rsquo;s Mac only, for now, as I don&rsquo;t have any other machines to test it on. It <em>might</em> work on Linux, but I wasn&rsquo;t going to promise that. Feel free to open a PR if you want to help me distribute it there.</p>
 ]]></content:encoded></item><item><title>A Digital Reset</title><enclosure url="https://chriswiegman.com/images/2026/03/a-digital-reset_hu_bebd7634bc8144ed.jpeg" length="3605094" type="image/jpeg"/><link>https://chriswiegman.com/2026/03/a-digital-reset/</link><guid isPermaLink="true">https://chriswiegman.com/2026/03/a-digital-reset/</guid><dc:creator>Chris Wiegman</dc:creator><category>Personal</category><category>Social Media</category><category>Digital Life</category><pubDate>Mon, 23 Mar 2026 08:00:00 -0500</pubDate><description/><content:encoded><![CDATA[ <p>Every once in a while, I reset my digital life. It&rsquo;s a habit that goes back to 2010, when I first cleared my Twitter post history, and over time it has grown in scope. My latest reset was this past week.</p>
<h2 id="why-cleaning-up-online-is-good-for-me">Why Cleaning Up Online Is Good For Me</h2>
<p>There are a lot of reasons I regularly clean up my digital life, but they can all be summed up in one sentence: what I put online, especially in places I don’t own, has no reason to be permanent.</p>
<p>Arguments, low-effort posts, drunk posts, and general shitposts have no reason to last. They offer no value to me or anyone else, and they often are not even an accurate reflection of what I believe, whether because my views have changed or because I posted out of anger or under the influence.</p>
<p>Work is one example. I’ve had plenty of jobs over the years, and few of those companies are places I would want to promote today. Removing those references from old posts feels right. The same goes for posts I made after a night out. None of that needs to stay online.</p>
<p>In the end, I want what I post online to be something I’m proud of. Anything that falls short of that is not worth keeping, and a reset helps me get back to that place.</p>
<h2 id="what-i-cleaned-up">What I Cleaned Up</h2>
<p>I cleaned up more than ever this year:</p>
<ul>
<li>I deleted all posts on Mastodon, Bluesky, and LinkedIn</li>
<li>I reset my watch history on all our streaming services</li>
<li>I logged my devices out of every account that keeps a login history</li>
<li>I wiped my music and podcast libraries</li>
<li>I wiped my Apple Health history</li>
<li>I deleted more than two dozen accounts</li>
<li>I removed apps and configurations I was no longer using</li>
<li>I wiped my Feedbin library</li>
<li>I cleared my location histories on every service I know of that keeps them</li>
<li>I cleared history of games and other apps</li>
</ul>
<p>Basically, if it was data about me that I could access and delete, I did. This year&rsquo;s reset was even more extreme than previous ones, where I kept things like my Apple Health database. In fact, the only things I tried to preserve this time were this site and my reading history.</p>
<p>Now it’s time to move forward again, at least until the next reset. I’m happy with it. It feels like a clean slate, and I’m looking forward to making the best of it.</p>
 ]]></content:encoded></item><item><title>Basic Stats for This Site</title><enclosure url="https://chriswiegman.com/images/2026/02/basic-stats-for-this-site_hu_bb95edf267750569.jpg" length="237620" type="image/jpeg"/><link>https://chriswiegman.com/2026/02/basic-stats-for-this-site/</link><guid isPermaLink="true">https://chriswiegman.com/2026/02/basic-stats-for-this-site/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Blogging</category><pubDate>Mon, 23 Feb 2026 08:00:00 -0600</pubDate><description/><content:encoded><![CDATA[ <p>After publishing on this site for the last two months I realized there is something I’m missing, basic stats.</p>
<p>It might be vanity, but I really do find it useful to know which pages are actually getting traffic and what content people seem to care about. This isn’t the type of blog that many people will read regularly, most of the traffic has always been from searching for a topic from a single post, but knowing what folks are looking for is still valuable.</p>
<p>I used to write almost entirely about WordPress. That’s no longer the case and I can see it in the stats. Tricks from <a href="https://chriswiegman.com/2025/08/disabling-copilot-in-vscode-an-update/">turning off AI in VS Code</a> and <a href="https://chriswiegman.com/2022/01/installing-node-lts-with-the-latest-npm-using-nvm/">installing Node with the latest NPM</a> are some of my most popular posts today and that’s helpful.</p>
<p>The catch is I haven’t been willing to install a stats product that either comes with a heavy cost monetarily or to your privacy. To get around that I’ve spent the last few weeks creating my own stats package and I’m pretty happy with the results.</p>
<p>I host this site with <a href="https://www.cloudflare.com/products/workers/">Cloudflare Workers</a>. It’s good at static sites like this but it offers so much more. I decided to take advantage of that and use its analytics engine to build stats collecting a few things, all without JavaScript, cookies or beacons of any kind.</p>
<p>Today I’m collecting basic URL information and a few other items, such as country and referrer, that Cloudflare already had available. I then built my own dashboard to display it all (of course I updated the privacy policy to go with it). The data is minimal. I don’t even collect the full user agent or IP address, and it helps me see what people are going to and where they’re coming from.</p>
<p>While I have no intention of monetizing this site, knowing what content people actually care about is still important to me. I’m hoping these new stats can help me find motivation for better content as well as to improve some of the posts that already exist. Either way, though, I’m excited about it and had a lot of fun putting it together.</p>
 ]]></content:encoded></item><item><title>You Probably Don’t Need a Content Management System</title><enclosure url="https://chriswiegman.com/images/2026/02/you-probably-dont-need-a-cms_hu_94dac151301637c2.jpg" length="386194" type="image/jpeg"/><link>https://chriswiegman.com/2026/02/you-probably-dont-need-a-content-management-system/</link><guid isPermaLink="true">https://chriswiegman.com/2026/02/you-probably-dont-need-a-content-management-system/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Blogging</category><pubDate>Wed, 18 Feb 2026 08:00:00 -0600</pubDate><description/><content:encoded><![CDATA[ <p>You Probably don’t need a full content management system.</p>
<p>Just six months ago I didn’t believe that myself and it caused me to make the mistake of <a href="https://chriswiegman.com/2025/08/a-new-site-for-better-content-management/">moving this site back to WordPress</a>. It’s true though.</p>
<p>The thing is most sites aren’t really managing content at all, at least not at a scale that requires the complexity and issues that come with something like WordPress. What they need instead is a good workflow to enable the user to say what they want to say.</p>
<p>Today I’ve found that workflow using <a href="https://gohugo.io">Hugo</a>. I can write, edit and work with the 700+ posts and pages on this site easily and still publish something fast on hosting that costs me nothing other than the domain renewal.</p>
<p>Of course, some sites really do need more, but that’s the exception rather than the rule.</p>
<p>If you enjoy blogging and really want to focus on the writing, spend some time with a site software that focuses on letting you write without friction. If you focus on the workflow of writing you really don’t need a content management system at all.</p>
 ]]></content:encoded></item><item><title>Travel for Work and Pleasure</title><enclosure url="https://chriswiegman.com/images/2026/02/travel-for-work-and-pleasure_hu_5b97cceb3c3b06e3.jpg" length="4688899" type="image/jpeg"/><link>https://chriswiegman.com/2026/02/travel-for-work-and-pleasure/</link><guid isPermaLink="true">https://chriswiegman.com/2026/02/travel-for-work-and-pleasure/</guid><dc:creator>Chris Wiegman</dc:creator><category>Personal</category><category>Travel</category><pubDate>Mon, 16 Feb 2026 08:26:25 -0600</pubDate><description/><content:encoded><![CDATA[ <p>Saturday night we returned home from our first vacation in almost 3 years. Our last trip, <a href="https://chriswiegman.com/2023/03/porto-portugal-our-1st-trip-abroad-since-2018/">to Porto in 2023</a>, was more of a fact-finding trip in hopes that we could move abroad. Unfortunately life had a different idea and <a href="https://chriswiegman.com/2023/04/i-miss-you-mom/">my mom passed away</a> a few weeks after we got home, changing the trajectory of a lot of things for us.</p>
<p>These days we&rsquo;re pretty happy in Chicago and, thankfully, don&rsquo;t feel like we <em>need</em> travel to escape where we live. That said, we both love to get out and see new cities and hope that, one day, we will be able to finish that move to Porto.</p>
<p>For this trip I was actually gone for 2 weeks, a week in Athens for work and then a week for vacation in Rome. Joy flew out to meet me and we made the best of every moment.</p>
<h2 id="work-travel-to-athens">Work Travel to Athens</h2>
<p>I wasn&rsquo;t sure what to expect of Athens but I really found that I loved the city in my short time there. We were there for my company&rsquo;s annual on-site trip and I was able to spend a few days getting to know all my new teams as well as exploring the city when our meetings were done. We talked about our roadmap for the next year, how we want to move forward and then we toured ancient Athens, including the Acropolis. It was amazing.</p>
<figure>
    <img class="with-caption" width="4032" height="3024" src="https://chriswiegman.com/images/2026/02/meeting-with-the-platform-team.jpeg" alt="My original team at Fingerprint with the Acropolis in the background" decoding="async" loading="lazy" srcset="https://chriswiegman.com/images/2026/02/meeting-with-the-platform-team_hu_9859e00e94b1903e.webp 850w,https://chriswiegman.com/images/2026/02/meeting-with-the-platform-team_hu_e7015ea1446cc63d.webp 710w,https://chriswiegman.com/images/2026/02/meeting-with-the-platform-team_hu_3481333f3cb2142.webp 300w" sizes="(max-width: 300px) 300w, (max-width: 710px) 710w, 850w" /><figcaption>My original team at Fingerprint with the Acropolis in the background</figcaption></figure><p>While I love working from home and wouldn&rsquo;t easily trade it for an office again, a successful remote team still requires some regular face time. For the first time since before Covid I feel like I&rsquo;m in an organization that understands that fact and I&rsquo;m grateful for it. Tomorrow I&rsquo;ll return to work refreshed and ready to move forward, but still able to work where I work best.</p>
<p>Meeting the team had me reviewing the past year, especially when I went into the office. In some ways I think that was very successful. Most of the Chicago tech folks I got to know I met by going into that office. That said, to pretend going into an office is more productive is just a fantasy. Good leadership hires good people and empowers them to work where they&rsquo;re most productive. It would take me a lot to give that up again.</p>
<p>After work and meetings I loved getting to see Athens, a city I&rsquo;ll admit I knew little about. Each of the 3 days of the on-site we were able to pick events to attend in the afternoon. I spent one day touring the city by foot, another at the archaeology museum and the third touring the Acropolis itself. As someone who is increasingly more fascinated with history than tech, it was amazing. I&rsquo;ll look forward to going back, next time with Joy.</p>
<figure>
    <img class="with-caption" width="2316" height="3088" src="https://chriswiegman.com/images/2026/02/me-in-front-of-the-parthenon.jpeg" alt="A selfie of me in front of the Parthenon during our tour" decoding="async" loading="lazy" srcset="https://chriswiegman.com/images/2026/02/me-in-front-of-the-parthenon_hu_b842e28616d21858.webp 850w,https://chriswiegman.com/images/2026/02/me-in-front-of-the-parthenon_hu_c4b7fce98b8bf41f.webp 710w,https://chriswiegman.com/images/2026/02/me-in-front-of-the-parthenon_hu_65ffcec4faa81ecd.webp 300w" sizes="(max-width: 300px) 300w, (max-width: 710px) 710w, 850w" /><figcaption>Me in front of the Parthenon during our tour</figcaption></figure><h2 id="on-to-rome">On to Rome</h2>
<p>As much as I enjoyed my time in Athens, the week came to an end all-too-quickly and then it was on to vacation.</p>
<p>We originally planned on going to Spain, a place my wife and I would both love to see and a place we might eventually consider moving to. Unfortunately flights and timing just weren&rsquo;t working out so instead we decided on Rome, a city that&rsquo;s been on my bucket list for as long as I can remember.</p>
<p>It didn&rsquo;t disappoint.</p>
<p>From the time we arrived on 06 February until the time we left on 14 February we were moving. I averaged 17,000 steps per day (about 12,000 more than I averaged the past couple of months at home). We saw all <a href="https://en.wikipedia.org/wiki/Basilicas_in_the_Catholic_Church#Major_basilicas">4 major basilicas</a>, walked the spine of the <a href="https://en.wikipedia.org/wiki/Circus_Maximus">Circus Maximus</a>, toured the city walls and the forum and so much more. We even saw Pope Leo XIV speak to the crowd at St. Peter&rsquo;s Square. I took almost 2,000 photos while we were there and I loved every minute of it.</p>
<figure>
    <img class="with-caption" width="4284" height="5712" src="https://chriswiegman.com/images/2026/02/circus-maximus.jpg" alt="Looking out over Circus Maximus on a sunny day" decoding="async" loading="lazy" srcset="https://chriswiegman.com/images/2026/02/circus-maximus_hu_f3361ca3a422980.webp 850w,https://chriswiegman.com/images/2026/02/circus-maximus_hu_d01d5cee9653c461.webp 710w,https://chriswiegman.com/images/2026/02/circus-maximus_hu_63cfba1d19cc1138.webp 300w" sizes="(max-width: 300px) 300w, (max-width: 710px) 710w, 850w" /><figcaption>Looking down the spine of the Circus Maximus</figcaption></figure><figure>
    <img class="with-caption" width="3024" height="4032" src="https://chriswiegman.com/images/2026/02/the-pope-speaks.jpeg" alt="Pope Leo XIV speaks from the window of the Apostolic Palace" decoding="async" loading="lazy" srcset="https://chriswiegman.com/images/2026/02/the-pope-speaks_hu_42cad0568a9223e5.webp 850w,https://chriswiegman.com/images/2026/02/the-pope-speaks_hu_719951ce7636451b.webp 710w,https://chriswiegman.com/images/2026/02/the-pope-speaks_hu_cbd3f3dc9675a64a.webp 300w" sizes="(max-width: 300px) 300w, (max-width: 710px) 710w, 850w" /><figcaption>Pope Leo XIV speaks to the crowd on St. Peter&#39;s Square during Angelus</figcaption></figure><p>As a Catholic myself it was all such an amazing experience. Though I enjoy our lives in Chicago, when it was time to come home on Saturday I was definitely not ready for it.</p>
<h2 id="back-to-reality">Back to Reality</h2>
<p>As I write this on our second day back home I&rsquo;m already looking forward to planning our next trip, wherever that might be. We will return to both Athens and Rome at some point, though, and I can&rsquo;t wait for all of it.</p>
 ]]></content:encoded></item><item><title>Better Taxonomy Management for Hugo with VS Code</title><enclosure url="https://chriswiegman.com/images/2026/02/better-tax-mgmt-for-hugo-with-vscode_hu_22286ad268e7faf1.jpg" length="133779" type="image/jpeg"/><link>https://chriswiegman.com/2026/02/better-taxonomy-management-for-hugo-with-vs-code/</link><guid isPermaLink="true">https://chriswiegman.com/2026/02/better-taxonomy-management-for-hugo-with-vs-code/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Tools</category><category>Blogging</category><pubDate>Mon, 02 Feb 2026 08:00:00 -0600</pubDate><description>Learn how to maintain consistent Hugo categories and tags using VS Code snippets and autocomplete for better content organization.</description><content:encoded><![CDATA[ <p>One of the things I need to be careful about when using <a href="https://gohugo.io">Hugo</a> is making sure the categories and tags used on a post stay consistent. In the past, even with WordPress, I had hundreds of tags and even extra categories that either didn&rsquo;t make sense or were there because I screwed something up, like a minor typo in the tag name.</p>
<p>With a little bit of work I now have a solution that works for me well in <a href="https://code.visualstudio.com/">VS Code</a> by letting me autocomplete the tags and categories I&rsquo;m already using throughout this site. I just start typing and it lets me know if a tag has been used before or not.</p>
<p>Here&rsquo;s how it works:</p>
<h2 id="extract-the-tags-and-categories-and-save-them-to-json">Extract the Tags and Categories and save Them to JSON</h2>
<p>The first piece is a Node.js script that extracts all the tags and categories and saves them to <code>.vscode/tags-categories.json</code> and <code>.vscode/markdown.code-snippets</code>.</p>
<p>The former file I use for reporting. The latter is what actually does the magic in VS Code.</p>
<p>Save this to <code>scripts/extract-tags.js</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env node
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Extracts all unique categories and tags from published Hugo posts.
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Outputs a JSON file that can be used for autocomplete/reference.
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&#34;fs&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&#34;path&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">CONTENT_DIR</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="s2">&#34;..&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">,</span> <span class="s2">&#34;posts&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">OUTPUT_FILE</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nx">__dirname</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;..&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;.vscode&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;tags-categories.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">SNIPPETS_FILE</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nx">__dirname</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;..&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;.vscode&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;markdown.code-snippets&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Extracts frontmatter from a markdown file
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">extractFrontmatter</span><span class="p">(</span><span class="nx">content</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">match</span> <span class="o">=</span> <span class="nx">content</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="sr">/^---\n([\s\S]*?)\n---/</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">match</span> <span class="o">?</span> <span class="nx">match</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">:</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Parses YAML-style list items from frontmatter
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">parseListItems</span><span class="p">(</span><span class="nx">frontmatter</span><span class="p">,</span> <span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">items</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">blockRegex</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">RegExp</span><span class="p">(</span><span class="sb">`^</span><span class="si">${</span><span class="nx">key</span><span class="si">}</span><span class="sb">:\\s*\\n((\\s*-\\s*.+\\n)+)`</span><span class="p">,</span> <span class="s2">&#34;m&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">match</span> <span class="o">=</span> <span class="nx">frontmatter</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="nx">blockRegex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">match</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">lines</span> <span class="o">=</span> <span class="nx">match</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">split</span><span class="p">(</span><span class="s2">&#34;\n&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">line</span> <span class="k">of</span> <span class="nx">lines</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">itemMatch</span> <span class="o">=</span> <span class="nx">line</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="sr">/^\s*-\s*(.+)\s*$/</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">itemMatch</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">itemMatch</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">trim</span><span class="p">().</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/^[&#34;&#39;]|[&#34;&#39;]$/g</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">items</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">items</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Recursively finds all markdown files
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">findMarkdownFiles</span><span class="p">(</span><span class="nx">dir</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">files</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">entries</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readdirSync</span><span class="p">(</span><span class="nx">dir</span><span class="p">,</span> <span class="p">{</span> <span class="nx">withFileTypes</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">entry</span> <span class="k">of</span> <span class="nx">entries</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">fullPath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">dir</span><span class="p">,</span> <span class="nx">entry</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">entry</span><span class="p">.</span><span class="nx">isDirectory</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">files</span><span class="p">.</span><span class="nx">push</span><span class="p">(...</span><span class="nx">findMarkdownFiles</span><span class="p">(</span><span class="nx">fullPath</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">entry</span><span class="p">.</span><span class="nx">isFile</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="nx">entry</span><span class="p">.</span><span class="nx">name</span><span class="p">.</span><span class="nx">endsWith</span><span class="p">(</span><span class="s2">&#34;.md&#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">files</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">fullPath</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">files</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Main extraction logic
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">extractTagsAndCategories</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">categories</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Set</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">tags</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Set</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">categoryCount</span> <span class="o">=</span> <span class="p">{};</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">tagCount</span> <span class="o">=</span> <span class="p">{};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">files</span> <span class="o">=</span> <span class="nx">findMarkdownFiles</span><span class="p">(</span><span class="nx">CONTENT_DIR</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`Found </span><span class="si">${</span><span class="nx">files</span><span class="p">.</span><span class="nx">length</span><span class="si">}</span><span class="sb"> markdown files`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">file</span> <span class="k">of</span> <span class="nx">files</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">file</span><span class="p">,</span> <span class="s2">&#34;utf8&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">frontmatter</span> <span class="o">=</span> <span class="nx">extractFrontmatter</span><span class="p">(</span><span class="nx">content</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">frontmatter</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Extract categories
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">fileCategories</span> <span class="o">=</span> <span class="nx">parseListItems</span><span class="p">(</span><span class="nx">frontmatter</span><span class="p">,</span> <span class="s2">&#34;categories&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">cat</span> <span class="k">of</span> <span class="nx">fileCategories</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">categories</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">cat</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="nx">categoryCount</span><span class="p">[</span><span class="nx">cat</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="nx">categoryCount</span><span class="p">[</span><span class="nx">cat</span><span class="p">]</span> <span class="o">||</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Extract tags
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">fileTags</span> <span class="o">=</span> <span class="nx">parseListItems</span><span class="p">(</span><span class="nx">frontmatter</span><span class="p">,</span> <span class="s2">&#34;tags&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">tag</span> <span class="k">of</span> <span class="nx">fileTags</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">tags</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">tag</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="nx">tagCount</span><span class="p">[</span><span class="nx">tag</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="nx">tagCount</span><span class="p">[</span><span class="nx">tag</span><span class="p">]</span> <span class="o">||</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Sort by usage count (most used first)
</span></span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">sortedCategories</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">categories</span><span class="p">).</span><span class="nx">sort</span><span class="p">((</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="nx">categoryCount</span><span class="p">[</span><span class="nx">b</span><span class="p">]</span> <span class="o">||</span> <span class="mi">0</span><span class="p">)</span> <span class="o">-</span> <span class="p">(</span><span class="nx">categoryCount</span><span class="p">[</span><span class="nx">a</span><span class="p">]</span> <span class="o">||</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">sortedTags</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">tags</span><span class="p">).</span><span class="nx">sort</span><span class="p">((</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="nx">tagCount</span><span class="p">[</span><span class="nx">b</span><span class="p">]</span> <span class="o">||</span> <span class="mi">0</span><span class="p">)</span> <span class="o">-</span> <span class="p">(</span><span class="nx">tagCount</span><span class="p">[</span><span class="nx">a</span><span class="p">]</span> <span class="o">||</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">categories</span><span class="o">:</span> <span class="nx">sortedCategories</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">tags</span><span class="o">:</span> <span class="nx">sortedTags</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">stats</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">categoryCount</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">tagCount</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">totalCategories</span><span class="o">:</span> <span class="nx">categories</span><span class="p">.</span><span class="nx">size</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">totalTags</span><span class="o">:</span> <span class="nx">tags</span><span class="p">.</span><span class="nx">size</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">totalFiles</span><span class="o">:</span> <span class="nx">files</span><span class="p">.</span><span class="nx">length</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">generatedAt</span><span class="o">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Generate snippet prefixes for a tag/category
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">generatePrefixes</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">lower</span> <span class="o">=</span> <span class="nx">name</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">prefixes</span> <span class="o">=</span> <span class="p">[</span><span class="nx">lower</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Add concatenated version for multi-word items (e.g., &#34;Digital Life&#34; -&gt; &#34;digitallife&#34;)
</span></span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">name</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="s2">&#34; &#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">concat</span> <span class="o">=</span> <span class="nx">lower</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\s+/g</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">concat</span> <span class="o">!==</span> <span class="nx">lower</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">prefixes</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">concat</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Add common abbreviations
</span></span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">abbrevMap</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Technology</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;tech&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Personal</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;pers&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nx">WordPress</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;wp&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Infrastructure</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;infra&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Open Source&#34;</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;oss&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Reflection</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;reflect&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Web Development&#34;</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;webdev&#34;</span><span class="p">,</span> <span class="s2">&#34;web&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Development</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;dev&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Digital Life&#34;</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;digital&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Social Media&#34;</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;social&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Education</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;edu&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Self-hosting&#34;</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;selfhost&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Content Management Systems&#34;</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;cms&#34;</span><span class="p">,</span> <span class="s2">&#34;contentmanagement&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">abbrevMap</span><span class="p">[</span><span class="nx">name</span><span class="p">])</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">prefixes</span><span class="p">.</span><span class="nx">push</span><span class="p">(...</span><span class="nx">abbrevMap</span><span class="p">[</span><span class="nx">name</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Return single string if only one prefix, otherwise return array
</span></span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">prefixes</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">1</span> <span class="o">?</span> <span class="nx">prefixes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">:</span> <span class="nx">prefixes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Generate VS Code snippets from tags and categories
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">generateSnippets</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">snippets</span> <span class="o">=</span> <span class="p">{};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Generate category snippets
</span></span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">category</span> <span class="k">of</span> <span class="nx">data</span><span class="p">.</span><span class="nx">categories</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">snippets</span><span class="p">[</span><span class="sb">`Category: </span><span class="si">${</span><span class="nx">category</span><span class="si">}</span><span class="sb">`</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">prefix</span><span class="o">:</span> <span class="nx">generatePrefixes</span><span class="p">(</span><span class="nx">category</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nx">body</span><span class="o">:</span> <span class="nx">category</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">description</span><span class="o">:</span> <span class="sb">`Category: </span><span class="si">${</span><span class="nx">category</span><span class="si">}</span><span class="sb">`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Generate tag snippets
</span></span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">tag</span> <span class="k">of</span> <span class="nx">data</span><span class="p">.</span><span class="nx">tags</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">snippets</span><span class="p">[</span><span class="sb">`Tag: </span><span class="si">${</span><span class="nx">tag</span><span class="si">}</span><span class="sb">`</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">prefix</span><span class="o">:</span> <span class="nx">generatePrefixes</span><span class="p">(</span><span class="nx">tag</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nx">body</span><span class="o">:</span> <span class="nx">tag</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">description</span><span class="o">:</span> <span class="sb">`Tag: </span><span class="si">${</span><span class="nx">tag</span><span class="si">}</span><span class="sb">`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">snippets</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Main execution
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;Extracting tags and categories from published posts...&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">extractTagsAndCategories</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Ensure .vscode directory exists
</span></span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">vscodePath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">dirname</span><span class="p">(</span><span class="nx">OUTPUT_FILE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">fs</span><span class="p">.</span><span class="nx">existsSync</span><span class="p">(</span><span class="nx">vscodePath</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fs</span><span class="p">.</span><span class="nx">mkdirSync</span><span class="p">(</span><span class="nx">vscodePath</span><span class="p">,</span> <span class="p">{</span> <span class="nx">recursive</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Write JSON data file
</span></span></span><span class="line"><span class="cl">  <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="nx">OUTPUT_FILE</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Generate and write snippets file
</span></span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">snippets</span> <span class="o">=</span> <span class="nx">generateSnippets</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="nx">SNIPPETS_FILE</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">snippets</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;\nResults:&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`  Categories: </span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">stats</span><span class="p">.</span><span class="nx">totalCategories</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`  Tags: </span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">stats</span><span class="p">.</span><span class="nx">totalTags</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`  Files processed: </span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">stats</span><span class="p">.</span><span class="nx">totalFiles</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`\nOutput written to:`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`  - </span><span class="si">${</span><span class="nx">OUTPUT_FILE</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`  - </span><span class="si">${</span><span class="nx">SNIPPETS_FILE</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;\nMost used categories:&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">data</span><span class="p">.</span><span class="nx">categories</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">).</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">cat</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`  - </span><span class="si">${</span><span class="nx">cat</span><span class="si">}</span><span class="sb"> (</span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">stats</span><span class="p">.</span><span class="nx">categoryCount</span><span class="p">[</span><span class="nx">cat</span><span class="p">]</span><span class="si">}</span><span class="sb"> posts)`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;\nMost used tags:&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">data</span><span class="p">.</span><span class="nx">tags</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">).</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">tag</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`  - </span><span class="si">${</span><span class="nx">tag</span><span class="si">}</span><span class="sb"> (</span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">stats</span><span class="p">.</span><span class="nx">tagCount</span><span class="p">[</span><span class="nx">tag</span><span class="p">]</span><span class="si">}</span><span class="sb"> posts)`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">main</span><span class="p">();</span></span></span></code></pre></div><h2 id="running-the-script">Running the Script</h2>
<p>I have three ways to run the script above to update the code snippets. I use a VS Code Task, the terminal via a Make target, or automatically every week via GitHub action. Frankly, the GitHub action is enough but the other options just make it easier for me if I know I&rsquo;ve changed the list.</p>
<h3 id="makefile">Makefile</h3>
<p>Add the following to <code>Makefile</code> in your project&rsquo;s root:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">extract</span>-<span class="n">tags</span>
</span></span><span class="line"><span class="cl"><span class="nf">extract-tags</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	node scripts/extract-tags.js
</span></span></code></pre></div><h3 id="vs-code-task">VS Code Task</h3>
<p>This one you&rsquo;ll save to <code>.vscode/tasks.json</code>. You can then run it by selecting &ldquo;Run Task&rdquo; from the command palette.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-JSON" data-lang="JSON"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;2.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;tasks&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;label&#34;</span><span class="p">:</span> <span class="s2">&#34;Extract Tags&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;shell&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;make extract-tags&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;problemMatcher&#34;</span><span class="p">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="github-action">GitHub Action</h3>
<p>Finally, this GitHub Action will run every Saturday, or you can trigger it manually by visiting your repo&rsquo;s Actions page on the GitHub website:</p>
<p>Save this as <code>.github/workflows/update-taxonomies.yml</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Update Taxonomy Database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schedule</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Run every Saturday at 00:00 UTC</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0 0 * * 6&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">workflow_dispatch</span><span class="p">:</span><span class="w"> </span><span class="c"># Allow manual triggering</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">permissions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="l">write</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">update-tags</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Checkout</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up Node.js</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-node@v6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">node-version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;lts/*&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Run extract-tags script</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">node scripts/extract-tags.js</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Commit changes</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">EndBug/add-and-commit@v9</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">add</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;.vscode/tags-categories.json .vscode/markdown.code-snippets&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">default_author</span><span class="p">:</span><span class="w"> </span><span class="l">github_actions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Update tags and categories database&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">committer_name</span><span class="p">:</span><span class="w"> </span><span class="l">GitHub Actions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">committer_email</span><span class="p">:</span><span class="w"> </span><span class="l">actions@github.com</span></span></span></code></pre></div><h2 id="updating-your-project-settings">Updating Your Project Settings</h2>
<p>Once you can run the script you need to update your project&rsquo;s settings in VS Code to actually trigger the snippets.</p>
<p>Save the following to <code>.vscode/settings.json</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;[markdown]&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;editor.quickSuggestions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;other&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;comments&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;strings&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;editor.snippetSuggestions&#34;</span><span class="p">:</span> <span class="s2">&#34;top&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;editor.suggest.showSnippets&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;editor.wordBasedSuggestions&#34;</span><span class="p">:</span> <span class="s2">&#34;off&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="wrapping-up">Wrapping Up</h2>
<p>That&rsquo;s it. Now VS Code will automatically complete your tags and categories based on those already in use around the site. This approach has helped me maintain a much cleaner site structure, preventing the tag sprawl I experienced in the past. The autocomplete makes it easy to reuse existing tags rather than accidentally creating duplicates with slight variations.</p>
 ]]></content:encoded></item><item><title>Time To Move</title><enclosure url="https://chriswiegman.com/images/2026/01/time-to-move_hu_be9f4df125652d2e.jpeg" length="735629" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/time-to-move/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/time-to-move/</guid><dc:creator>Chris Wiegman</dc:creator><category>Personal</category><category>Travel</category><pubDate>Fri, 30 Jan 2026 07:14:38 -0600</pubDate><description/><content:encoded><![CDATA[ <p>I might be a bit quiet here for the next few weeks. We&rsquo;re not moving house again, but I do get to leave the house for a bit.</p>
<p>Next week I&rsquo;ll be in Athens, Greece for our annual company all-hands. It&rsquo;s a new country for me with a company I haven&rsquo;t been at too long and I&rsquo;m really looking forward to it.</p>
<p>While I&rsquo;ve only been at <a href="https://fingerprint.com">Fingerprint</a> for about 5 months, sometimes it feels like I&rsquo;ve been here 5 years. That&rsquo;s not a bad thing. We&rsquo;ve come a long way with my teams and, though we still have a long way to go, I&rsquo;ve really enjoyed helping get where we&rsquo;re at today.</p>
<p>It&rsquo;s the first time I&rsquo;ve travelled to a new country without Joy (I&rsquo;ve been to Europe a few times solo, but always to places we&rsquo;ve already been) which is a bit weird but I&rsquo;ve promised her we&rsquo;ll make up for that later.</p>
<p>After the company all-hands I&rsquo;m going to take some much-needed down time, mostly with this computer off for a week or so. If I get to write some during that time, great, but it&rsquo;s not my priority for a change and I&rsquo;m excited about all of it.</p>
 ]]></content:encoded></item><item><title>On Writing and AI</title><enclosure url="https://chriswiegman.com/images/2026/01/on-writing_hu_3f5abc0e824b156b.jpg" length="242788" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/on-writing-and-ai/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/on-writing-and-ai/</guid><dc:creator>Chris Wiegman</dc:creator><category>Personal</category><category>Blogging</category><pubDate>Wed, 28 Jan 2026 08:00:00 -0600</pubDate><description/><content:encoded><![CDATA[ <p>For all the work I’ve done on improving my <a href="https://gohugo.io">Hugo</a> workflow, there’s one thing automation can’t do.</p>
<p>Automation can’t write for me.</p>
<p>I know, I know&hellip; AI is all the rage and I could find some agent to put out slop on this site, but that is precisely what I build this site to avoid.</p>
<p>Writing is the fun part.</p>
<p>A place for me to write is why this site exists.</p>
<p>I don’t write for a “side hustle” or to make money or to attract a following. I don’t even keep track of how many people view this site, much less do I sell anything on it.</p>
<p>I write because I enjoy writing and want to share what I’ve learned, sometimes for my own memory and other times because I hope a solution I’ve found can help someone else.</p>
<p>So, as great as AI probably is for some, it’s not for me, at least not for this site. Honestly, I hope if you’re reading this it isn’t for you either.</p>
 ]]></content:encoded></item><item><title>One Month Back on Hugo - It Was the Right Move</title><enclosure url="https://chriswiegman.com/images/2026/01/one-month-back-on-hugo-it-was-the-right-move_hu_973c1c9b07cb4e54.jpeg" length="2595390" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/one-month-back-on-hugo-it-was-the-right-move/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/one-month-back-on-hugo-it-was-the-right-move/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Blogging</category><category>Web Development</category><category>Tools</category><pubDate>Mon, 26 Jan 2026 08:00:00 -0600</pubDate><description>Reflections on using Hugo, one month after I moved this site back to it from WordPress.</description><content:encoded><![CDATA[ <p>This past weekend marked one month <a href="https://chriswiegman.com/2025/12/a-hopefully-final-move-back-to-hugo/">since I moved this site back to Hugo</a>. It was definitely the right move.</p>
<p>This is actually the third time I’ve moved this site to <a href="https://gohugo.io">Hugo</a>. Historically, I get about a month in and start thinking about going back to WordPress due to workflow issues. This time is different.</p>
<h2 id="working-on-this-site-has-been-fun-again">Working on this Site Has Been Fun Again</h2>
<p>First and foremost, working on this site has been fun again with Hugo. I’ve made over 300 commits since I started porting my old site to it. A few weeks ago I had <a href="https://chriswiegman.com/2025/12/a-new-type-of-development-for-me/">even started learning Swift and playing with apps for Apple</a>. I’ve enjoyed that, but I haven’t gotten far simply because I’ve had too much fun working on this site.</p>
<p>I’ve built the <a href="https://chriswiegman.com/library/">library</a> and <a href="https://chriswiegman.com/notes/">notes</a> pages. I’ve optimized and re-optimized the site templates and I’ve built numerous scripts to make writing easier.</p>
<p>I’ve rethought the content, moving short, personal posts to the “<a href="https://chriswiegman.com/notes/">notes</a>” page in order to make my RSS feed and blog easier for folks to read. I’ve also consolidated the tags on this site, going from almost 300 to about 35.</p>
<p>So far I’ve largely kept the design, but that will be updated too. For now I’ve just been optimizing the heck out of what I have with more semantic markup and leaner CSS.</p>
<p>Through all of it, I’ve had fun with the tech. That’s something I hadn’t really had with this site in a very long time.</p>
<h2 id="workflow-matters">Workflow Matters</h2>
<p>I <a href="https://chriswiegman.com/2026/01/my-2026-hugo-workflow/">wrote about my new Hugo workflow</a> a few weeks ago. I’ve automated draft creation and publishing so that I don’t have to worry about the little details that used to drive me crazy.</p>
<p>Since that post I’ve taken it further by implementing the <a href="https://chriswiegman.com/notes/">notes</a> page. This page takes all the content that really didn’t deserve to be called a blog post and adds it to a dedicated notes section, separate from the normal blog (though still available via <a href="https://chriswiegman.com/notes/index.xml">RSS</a>). This corrects a long-standing problem: me publishing what was essentially garbage just to say I published something on a given day.</p>
<p>Going forward I’ve even built the notes feature into my publishing script. Any “<a href="https://chriswiegman.com/categories/personal/">Personal</a>” post that is less than 80 words will automatically become a “note” upon publishing. In the end, this means the main feed will contain better content and I can easily publish the shorter, personal notes on the site.</p>
<p>In the end I’ve invested more in my workflow with this iteration of the site than I have with the site itself, and so far I’m really liking the results.</p>
<h2 id="performance-and-privacy-matter">Performance and Privacy Matter</h2>
<p>Finally, this site is far more performant and private than something built on WordPress and hosted on WordPress.com could ever be.</p>
<p>What started me on this path back away from WordPress was realizing just how bad WordPress had become for tracking. WordPress.com forces Jetpack onto sites and it was filled with scripts for user tracking, AI, and other garbage that I just didn’t need or want. While I had been able <a href="https://github.com/ChrisWiegman/chriswiegman-plugin/blob/c34eb0d437d869a745d40515164631370f8b9ba2/chriswiegman-plugin.php#L34">to remove some of that via code</a>, it wasn’t enough. On top of that, no matter how much I tweaked the site the WordPress Dashboard was pathetically slow, especially on my iPad, and writing became a more frustrating chore than something I enjoyed.</p>
<p>Then there was the WordPress theme. I actually did like the base theme I was using, OllieWP (if I was ever forced to use WordPress again, this is the theme I would choose). In the end, though, it couldn’t overcome the shortcomings of working with WordPress&rsquo; full site editor&rsquo;. For example, between the Dashboard performance problems and the ridiculous way editing actually works, it could take an hour or more for even the smallest changes. What’s worse is that there was no good way to roll anything back.</p>
<p>My final straw was trying to adjust the spacing between posts on the homepage. It took multiple screens, each requiring an independent save. In the end, I spent over 2 hours on it, hated the results, and then found that the only way I could roll it all back was to do a full database restore at the host level.</p>
<p>That’s just unacceptable.</p>
<p>Of course, then there were the front-end performance issues as well. Even though the page required no interactivity, I could never get it to load fewer than nearly a dozen JavaScript resources. Add in the CSS and other assets and the best I could get the page weight on the homepage was right around 500kb. That’s insane! Believe it or not, that was also an improvement from the 900kb it was before I started removing all the tracking scripts and other cruft.</p>
<p>Today the homepage of this site, with all resources loaded, is about 14kb. The content is the same as it was with WordPress but the bloat is just gone. This is good for everyone, especially those of y’all who don’t want to be tracked (there is nothing left for tracking other than logs my host, Cloudflare, might keep).</p>
<h2 id="this-site-is-finally-something-im-proud-of">This Site Is Finally Something I’m Proud Of</h2>
<p>In the end, after a month back on Hugo this site is finally something I’m proud of and can have fun with. It’s been a lot of years since I’ve been able to say that.</p>
<p>Building my own site is what originally attracted me to tech as a career. Compared to aviation, I was actually building something and it was amazing. The problem was that feeling of pride and wonder seemed to have died long ago.</p>
<p>Thankfully, neither was really dead. I just needed to think differently and spend some time focusing on what matters most to me: how I use my tools.</p>
 ]]></content:encoded></item><item><title>Preparing for Cleanup</title><enclosure url="https://chriswiegman.com/images/2026/01/preparing-for-cleanup_hu_be9785ea3a6a58bd.jpg" length="5850507" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/preparing-for-cleanup/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/preparing-for-cleanup/</guid><dc:creator>Chris Wiegman</dc:creator><category>Personal</category><category>Blogging</category><pubDate>Fri, 23 Jan 2026 06:54:03 -0600</pubDate><description/><content:encoded><![CDATA[ <p>In 2021 I decided to <a href="https://chriswiegman.com/2021/07/building-a-blogging-habit/">start posting on this site 3 days a week</a>. For the most part that strategy has worked well. It’s been something I enjoy and I’ve created a lot of content in doing so.</p>
<p>Some of my posts, however, have been anything but &ldquo;quality.&rdquo; When times were tough I would post anyway, rather than take a pause, just for the sake of posting.</p>
<p>That content isn’t helpful to you or even to me.</p>
<h2 id="its-time-to-cleanup-this-site">It’s Time to Cleanup this Site</h2>
<p>This weekend it will finally be time for me to clean up this site and delete the old cruft.</p>
<p>This content simply doesn’t need to be here anymore and, that’s OK.</p>
<p>It’s also time for me to look at what I am writing and to stop forcing myself from following a schedule.</p>
<p>Habits are good.</p>
<p>Habits for the sake of habits are not good.</p>
<p>Here’s to creating a sustainable future on this site that doesn’t waste your time or mine.</p>
 ]]></content:encoded></item><item><title>Automating Sustainable Draft Post Creation in Hugo</title><enclosure url="https://chriswiegman.com/images/2026/01/create-post-draft-in-github_hu_95cc7c426a02ea85.jpg" length="346452" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/automating-sustainable-draft-post-creation-in-hugo/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/automating-sustainable-draft-post-creation-in-hugo/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Blogging</category><category>Web Development</category><category>Tools</category><pubDate>Wed, 21 Jan 2026 08:00:00 -0600</pubDate><description/><content:encoded><![CDATA[ <p>I&rsquo;ve had a much better experience blogging with <a href="https://gohugo.io">Hugo</a> since <a href="https://chriswiegman.com/2025/12/a-hopefully-final-move-back-to-hugo/">coming back to the platform last month</a>. Most of this is due to some automation I&rsquo;ve added to help make drafting posts, and publishing those drafts, easier.</p>
<p>Last week I talked about <a href="https://chriswiegman.com/2026/01/deploying-my-hugo-site-to-cloudflare-workers/">deploying this site to Cloudflare workers</a>.</p>
<p>Today I want to share how I create a draft.</p>
<h2 id="the-problem-with-hugo-post-creation">The Problem with Hugo Post Creation</h2>
<p>Usually you add new content to a Hugo post <a href="https://gohugo.io/getting-started/quick-start/#add-content">with the CLI</a>. This works, but it has a few issues:</p>
<p>First, it assigns a date to posts immediately. This means you have something else to track if you&rsquo;re not going to publish your post right that moment.</p>
<p>Second, as Hugo works by managing files, the file structure can become a problem if you don&rsquo;t know what the title of the post will be when you create the draft. For example, I use the file format <code>/content/posts/&lt;YYYY&gt;/&lt;MM&gt;-&lt;DD&gt;-&lt;post title&gt;.md</code>. This means that, unless I know the publish date and the post title when I create the post I&rsquo;m going to have to edit the file later, something I often forget to do.</p>
<p>In addition, I&rsquo;ve often missed the <code>draft</code> front matter when trying to publish too many posts at once.</p>
<p>By themselves these aren&rsquo;t huge issues but they did add friction to my workflow. There had to be a better way.</p>
<h2 id="creating-and-automating-post-drafts">Creating and Automating Post Drafts</h2>
<p>I write my posts in one of two places: on my laptop with VS Code or on my iPad with iA Writer. To make this easier I wanted an automation that will work on either. Here&rsquo;s what that looks like:</p>
<ol>
<li>On my laptop I can trigger a new post draft from the terminal, from a VS Code task or from the GitHub website.</li>
<li>On my iPad I can trigger a new post draft from GitHub and just update the repo.</li>
</ol>
<p>Of course, creating the content is only 1/2 the solution. I also need to account for unknown dates and titles. As a result my post draft automation creates the draft with a time/date stamp for the filename in <code>/content/drafts</code>. It also doesn&rsquo;t assign a date at all to the front matter, it doesn&rsquo;t need it.</p>
<p>The filename, in this case, is completely arbitrary. I chose the stamp just so I could create multiple drafts without conflicts. If I then want to track the drafts more easily I can simply rename them. Given they&rsquo;re in the &ldquo;drafts&rdquo; folder this makes it easy. The correct filename and the <code>date</code> field in the front matter are then added when I publish the post later.</p>
<p>Here are the scripts I have to do this:</p>
<h3 id="the-draft-archetype">The Draft Archetype</h3>
<p>First, instead of going right to a new post, I have a <code>draft</code> content type in my Hugo theme. Here&rsquo;s the archetype file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: &#34;{{ replace .File.ContentBaseName <span class="sb">`-`</span> <span class="sb">` `</span> | title }}&#34;
</span></span><span class="line"><span class="cl">description: &#34;&#34;
</span></span><span class="line"><span class="cl">draft: true
</span></span><span class="line"><span class="cl">images:
</span></span><span class="line"><span class="cl">  <span class="k">-</span>
</span></span><span class="line"><span class="cl">categories:
</span></span><span class="line"><span class="cl">  <span class="k">-</span>
</span></span><span class="line"><span class="cl">tags:
</span></span><span class="line"><span class="cl">  -
</span></span><span class="line"><span class="cl">---</span></span></code></pre></div><p>Note a few things with this. First it uses the filename, which in my case is a time/date stamp, as the title. This works for me in making drafts easier to recognize. I simply change it when I know what I want the post title to be.</p>
<p>Second, it doesn&rsquo;t have a <code>date</code> field at all and the <code>draft</code> field is still set to true. Both of these are covered later by my publish script.</p>
<p>Finally, it contains empty items for images, categories and tags. I always want at least one in each post so that works well for me. If you don&rsquo;t always use them you&rsquo;ll want to adjust that.</p>
<h3 id="make-target">Make Target</h3>
<p>The method I use most to create the draft is a <a href="https://www.gnu.org/software/make/manual/make.html">Make</a> target that I can run from my terminal.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">draft</span>
</span></span><span class="line"><span class="cl"><span class="nf">draft</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	@file<span class="o">=</span>content/drafts/<span class="k">$(</span>shell date +<span class="s2">&#34;%Y%m%d-%H%M%S&#34;</span><span class="k">)</span>.md<span class="p">;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">	hugo new <span class="nv">$$</span>file<span class="p">;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">	perl -i -ne <span class="s1">&#39;print unless /^date:/&#39;</span> <span class="nv">$$</span>file<span class="p">;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">	code <span class="nv">$$</span>file
</span></span></code></pre></div><p>This uses Hugo&rsquo;s built-in content creation and explicitly removes any date generated. It also opens the file in VS Code which can be super handy.</p>
<h3 id="vs-code-task">VS Code Task</h3>
<p>If I want to, I don&rsquo;t even need to leave my editor, or go to its terminal, to create a draft. Instead I can just go to the command menu, select &ldquo;Tasks: Run Task&rdquo; and select &ldquo;Create Draft.&rdquo; That&rsquo;s it.</p>
<p>This works by creating the file <code>.vscode/tasks.json</code> in the root of your site&rsquo;s repo. Here&rsquo;s the contents of that task:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;2.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;tasks&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;label&#34;</span><span class="p">:</span> <span class="s2">&#34;Create Draft&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;shell&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;make draft&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;problemMatcher&#34;</span><span class="p">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Note all it does is make the draft and open it in a new tab in VS Code. Again it&rsquo;s a super-easy way to create the draft I need. Then I just need to commit it to my repo.</p>
<h3 id="github-action">GitHub Action</h3>
<p>The above works well, if I&rsquo;m on my laptop. On my iPad, however, it&rsquo;s a different story. Here I use a GitHub Action to create the draft and then just pull the changes onto my iPad.</p>
<p>To create this you&rsquo;ll want to create the file <code>.github/workflows/draft.yml</code> in your repo and then add the following to it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create Empty Post Draft</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">workflow_dispatch</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">permissions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="l">write</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">draft</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">fetch-depth</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create draft file</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">shell</span><span class="p">:</span><span class="w"> </span><span class="l">bash</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          set -euo pipefail
</span></span></span><span class="line"><span class="cl"><span class="sd">          TS=&#34;$(date -u +&#34;%Y%m%d-%H%M%S&#34;)&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">          FILE=&#34;content/drafts/${TS}.md&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          mkdir -p content/drafts
</span></span></span><span class="line"><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          cat &gt; &#34;$FILE&#34; &lt;&lt;EOF
</span></span></span><span class="line"><span class="cl"><span class="sd">          ---
</span></span></span><span class="line"><span class="cl"><span class="sd">          title: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">          draft: true
</span></span></span><span class="line"><span class="cl"><span class="sd">          images:
</span></span></span><span class="line"><span class="cl"><span class="sd">            - /images/
</span></span></span><span class="line"><span class="cl"><span class="sd">          description: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">          categories:
</span></span></span><span class="line"><span class="cl"><span class="sd">            -
</span></span></span><span class="line"><span class="cl"><span class="sd">          tags:
</span></span></span><span class="line"><span class="cl"><span class="sd">            -
</span></span></span><span class="line"><span class="cl"><span class="sd">          ---
</span></span></span><span class="line"><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          EOF
</span></span></span><span class="line"><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          echo &#34;Created $FILE&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">          echo &#34;FILE=$FILE&#34; &gt;&gt; &#34;$GITHUB_ENV&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">EndBug/add-and-commit@v9</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">default_author</span><span class="p">:</span><span class="w"> </span><span class="l">github_actions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">Add new post draft</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">committer_name</span><span class="p">:</span><span class="w"> </span><span class="l">GitHub Actions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">committer_email</span><span class="p">:</span><span class="w"> </span><span class="l">actions@github.com</span></span></span></code></pre></div><p>Note this doesn&rsquo;t use the Make target but it&rsquo;s <em>almost</em> the same. The only difference is it doesn&rsquo;t add a title to the front matter. You can run this by going to your actions, selecting the action, selecting &ldquo;Run Workflow&rdquo; and confirming it with the modal that pops up.</p>
<figure>
    <img class="with-caption" width="2786" height="1762" src="https://chriswiegman.com/images/2026/01/create-post-draft-in-github.png" alt="Screenshot of GitHub Action with the confirmation modal to run the workflow displayed" decoding="async" loading="lazy" srcset="https://chriswiegman.com/images/2026/01/create-post-draft-in-github_hu_7f6ee9e00771af84.webp 850w,https://chriswiegman.com/images/2026/01/create-post-draft-in-github_hu_5a7243f8768f87d7.webp 710w,https://chriswiegman.com/images/2026/01/create-post-draft-in-github_hu_65e567410b7fe8fe.webp 300w" sizes="(max-width: 300px) 300w, (max-width: 710px) 710w, 850w" /><figcaption>The GitHub Action screen with the confirmation modal displayed. Selecting &#39;Run Workflow&#39; here, the green button, will create a new draft and commit it to my repo.</figcaption></figure><p>Once you hit &ldquo;Run Workflow&rdquo; here the Action will take care of the rest, and I just need to pull it down to my iPad and start writing.</p>
<p>In the future I&rsquo;ll probably automate this even further with an iOS shortcut to create the draft. For now, however, this works really well.</p>
<p>Taken together, this gives me a much more sustainable model for draft creation on this site. In another post I&rsquo;ll then walk through how to publish a draft, either &ldquo;now&rdquo; or on a schedule.</p>
 ]]></content:encoded></item><item><title>Harper, My New Grammar Checker</title><enclosure url="https://chriswiegman.com/images/2026/01/harper-grammar-checker_hu_48239ca94d35272d.jpg" length="454285" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/harper-my-new-grammar-checker/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/harper-my-new-grammar-checker/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Blogging</category><category>Writing</category><category>Open Source</category><category>Privacy</category><pubDate>Mon, 19 Jan 2026 10:03:45 -0600</pubDate><description/><content:encoded><![CDATA[ <p>For years I&rsquo;ve used the popular <a href="https://streetsidesoftware.com/vscode-spell-checker/">Code Spell Checker</a> extension for <a href="https://code.visualstudio.com">VS Code</a>. While it&rsquo;s been great for spell checking, as I write more in VS Code, especially in using <a href="https://gohugo.io">Hugo</a> for this site, I&rsquo;ve wanted something that could check for grammar issues as well.</p>
<p>This isn&rsquo;t a new feature, services like Grammarly have, of course, been around for over a decade and then there are the built-in features of work processors like Google Docs. Those are great, but for this project they&rsquo;re just too much, especially in the age of AI. I don&rsquo;t want something to completely rewrite what I have here, I just want to cut down on some of the errors I regularly miss.</p>
<h2 id="enter-harper">Enter Harper</h2>
<p>This past weekend I finally found a solution that seems to be private enough for me while also catching more than just spelling mistakes. <a href="https://writewithharper.com">Harper</a> is an open source project that, so far, seems to capture all I need it to and is customizable enough that I can turn off bad features like its insistence on trying to enforce oxford commas everywhere.</p>
<p>If it has anything that gives me pause it is that it is run by Automattic, a company I once respected but has since <a href="https://techcrunch.com/2025/01/12/wordpress-vs-wp-engine-drama-explained/">proven</a> to be <a href="https://webhosting.today/2024/09/23/wordcamp-us-controversy-matt-mullenweg-targets-wp-engine-calling-it-the-cancer-of-wordpress/">anything</a> but a <a href="https://www.therepository.email/se-reed-accuses-matt-mullenweg-of-retaliation-after-wordpress-org-ban">good steward</a> of <a href="https://www.therepository.email/fork-or-fiction-wordpress-co-founder-bans-governance-advocates-in-latest-dispute">open source software</a>. That said, as long as it isn&rsquo;t sending any data back to Automattic it&rsquo;s still the best option I&rsquo;ve found, for now.</p>
<p>Grammar checking, as just a basic feature, seems to be a relic of a pre-AI past so finding something that still does it right, without features I don&rsquo;t need, has been very welcome.</p>
 ]]></content:encoded></item><item><title>Winter is Here</title><enclosure url="https://chriswiegman.com/images/2026/01/im-going-to-embrace-2026_hu_d945ab5bf1fe2cd7.jpeg" length="3378049" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/winter-is-here/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/winter-is-here/</guid><dc:creator>Chris Wiegman</dc:creator><category>Personal</category><category>Reflection</category><category>Home</category><pubDate>Fri, 16 Jan 2026 08:00:00 -0600</pubDate><description/><content:encoded><![CDATA[ <p>We’ve passed the holidays which means “real” winter has arrived in Chicago.</p>
<p>If there’s a time of year in Chicago that I don’t like, it’s the few weeks around mid-January where it’s too cold to do anything.</p>
<p>We don’t walk outside, we don’t go anywhere “fun” and sometimes we’ll even order our groceries out if the weather gets cold enough.</p>
<p>It’s not as bad as the endless summer of Florida was, but it’s still not a pleasant few weeks. At least I know it is only going to be a few weeks and better weather will be here soon.</p>
 ]]></content:encoded></item><item><title>I’ve Built a Library</title><enclosure url="https://chriswiegman.com/images/2026/01/i-built-a-library_hu_7df938e3a77c3bed.jpg" length="2232890" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/ive-built-a-library/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/ive-built-a-library/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Web Development</category><pubDate>Wed, 14 Jan 2026 08:00:00 -0600</pubDate><description/><content:encoded><![CDATA[ <p>I’ve built a <a href="https://chriswiegman.com/library/">library</a>!</p>
<p>One thing I enjoy about having this site is recording bits and pieces of my life. From my work I have my <a href="https://chriswiegman.com/speaking/">speaking page</a>. There’s also my <a href="https://chriswiegman.com/uses/">uses page</a> and my <a href="https://chriswiegman.com/now/">now page</a>, both of which I have the git history of so I can see changes to everything over time. To me they’re a fun record of where I’ve been and something I enjoy sharing with everyone.</p>
<p>I’m not a big fan of TV shows, nor do I watch many movies. Instead I read and I listen to music. For the latter I listen to too much to be able to record it all. For my books, however, I’ve been using <a href="https://www.goodreads.com">Goodreads</a> since 2010. It records most of what I’ve read, but over the years it’s lost features and even entire books I’ve recorded in it. To be honest, given the lack of investment in it I wouldn’t be surprised if it was eventually shut down.</p>
<p>This past week I finally copied my book data off of Goodreads to this site. I started this project in 2021 when I first exported my Goodreads data, but I never got around to implementing it.</p>
<p>Now I have a shell script that can process my Goodreads export file (it exports a .csv file) and saves each book and its data to markdown files on this site. The only thing it can’t do, as the export doesn’t include the data, is properly handle multiple readings of the same book but, as the read date is an array in front matter and I don’t read too many books more than once, that was easy to add manually.</p>
<p>Today I’m proud to launch this as a new feature on this site, my <a href="https://chriswiegman.com/library/">library page</a>. It contains a sortable list of every book Goodreads had going back to 2010 and a summary for my own visualization of how many books I’ve read each year.</p>
<p>It’s not something many people are going to be interested in, I realize that. But it is something I enjoy having to keep track of everything I’ve read over the years.</p>
<p>As for keeping it up to date, I’ll keep using Goodreads while it’s still there. My import script can take a fresh export and add the new books. I’ll try to do that monthly. I read so many books on my Kindle that this workflow will be easy to maintain either way.</p>
<p>These are the type of features that are so much fun to build and review over the years. I’m looking forward to seeing it grow and how my reading changes in the years ahead.</p>
 ]]></content:encoded></item><item><title>Deploying My Hugo Site to Cloudflare Workers</title><enclosure url="https://chriswiegman.com/images/2026/01/deploying-my-hugo-site-to-cloudflare-workers_hu_8343dc2cb7465285.jpg" length="804845" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/deploying-my-hugo-site-to-cloudflare-workers/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/deploying-my-hugo-site-to-cloudflare-workers/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Web Development</category><category>Tools</category><category>Infrastructure</category><pubDate>Mon, 12 Jan 2026 08:00:00 -0600</pubDate><description>Scripts and configuration to deploy a hugo site to Cloudflare workers using GitHub Actions</description><content:encoded><![CDATA[ <p>This is the first of a few posts I have planned on my new <a href="https://gohugo.io">Hugo</a> workflow. I’m starting here with the basics of getting the site itself running. In future posts I’ll cover a usable workflow to draft and publish content from any device, just like a fancy content management system would do.</p>
<p>When <a href="https://chriswiegman.com/2024/11/a-new-day-a-new-host/">I last had this site on Cloudflare</a> I used Cloudflare Pages. That worked well at the time but Cloudflare Pages isn&rsquo;t getting the attention it used to. Instead Cloudflare is focusing on its <a href="https://www.cloudflare.com/products/workers/">Workers</a> product which is generally more robust, but does pose a few challenges for deploying a static site such as this one which is made with Hugo.</p>
<p>It took a little trial and error, but now I’m pretty happy with the this site on Workers. The catch is the default configuration recommended by Cloudflare, where Workers connects directly to my repository just didn’t work for me.</p>
<p>What I needed was a few things:</p>
<ol>
<li>The ability to reliably deploy when new content is pushed (this is where Workers’ recommended connection to GitHub was fine)</li>
<li>The ability to easily deploy on demand for any reason I needed to</li>
<li>The ability to deploy the site on a schedule to handle scheduled content such as this post</li>
<li>Faster deploys than their default mechanism</li>
</ol>
<p>Thankfully, this wasn’t too hard to get. My configuration involves an updated <code>wrangler.toml</code> configuration as well as a GitHub Action that does all the actual work. Here’s the relevant files:</p>
<h2 id="the-wrangler-config">The Wrangler Config</h2>
<p><a href="https://developers.cloudflare.com/workers/wrangler/">Wrangler</a> serves as the controller, if you will, for Cloudflare Workers. It’s what starts the server and actually gets your code working. This is the big difference from Cloudflare Pages which just served any files it had automatically.</p>
<p>To run a Hugo site on Cloudflare workers add a file to the root of your site called <code>wrangler.toml</code> with the following configuration:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;chriswiegman-com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">compatibility_date</span> <span class="p">=</span> <span class="s2">&#34;2025-07-31&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">preview_urls</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">assets</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">directory</span> <span class="p">=</span> <span class="s2">&#34;./public&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">not_found_handling</span> <span class="p">=</span> <span class="s2">&#34;404-page&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">html_handling</span> <span class="p">=</span> <span class="s2">&#34;auto-trailing-slash&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div><p>The only thing you’re going to need to change for certain is the <code>name</code> field on line 1 to reflect the name of your worker. The rest should work fine with most Hugo builds.</p>
<h2 id="the-github-action">The GitHub Action</h2>
<p>Next up, the GitHub Action. For this you’ll want to save the following code to <code>.github/workflows/deploy.yml</code> in your GitHub repo:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span><span class="lnt">83
</span><span class="lnt">84
</span><span class="lnt">85
</span><span class="lnt">86
</span><span class="lnt">87
</span><span class="lnt">88
</span><span class="lnt">89
</span><span class="lnt">90
</span><span class="lnt">91
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy to Cloudflare Workers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">SITE_TITLE</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Chris Wiegman&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">SITE_URL</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://chriswiegman.com/&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">RSS_URL</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://chriswiegman.com/index.xml&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">branches</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">main]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">paths-ignore</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;content/drafts/**&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;.vscode/**&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;scripts/**&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;.github/**&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;Makefile&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;package.json&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;package-lock.json&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;README.md&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;.gitignore&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;/gitmodules&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">workflow_dispatch</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">workflow_call</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">inputs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">ref</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">schedule</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0 15 * * *&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">permissions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="l">read</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">concurrency</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="l">${{ github.workflow }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cancel-in-progress</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">deploy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Checkout</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">ref</span><span class="p">:</span><span class="w"> </span><span class="l">${{ inputs.ref }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">submodules</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">fetch-depth</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup Hugo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-hugo@v3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">hugo-version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;latest&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">extended</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Hugo resources</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                      resources</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.os }}-hugo-resources-${{ hashFiles(&#39;**/go.mod&#39;, &#39;**/go.sum&#39;, &#39;**/package-lock.json&#39;, &#39;**/pnpm-lock.yaml&#39;, &#39;**/yarn.lock&#39;, &#39;hugo.toml&#39;, &#39;hugo.yaml&#39;, &#39;hugo.json&#39;, &#39;config/**&#39;, &#39;assets/**&#39;) }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                      ${{ runner.os }}-hugo-resources-</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Hugo cacheDir</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                      .hugo_cache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.os }}-hugo-cachedir-${{ hashFiles(&#39;**/go.mod&#39;, &#39;**/go.sum&#39;, &#39;hugo.toml&#39;, &#39;hugo.yaml&#39;, &#39;hugo.json&#39;, &#39;config/**&#39;) }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                      ${{ runner.os }}-hugo-cachedir-</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build site</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">hugo --gc --minify --cacheDir &#34;${{ github.workspace }}/.hugo_cache&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up Node (with npm cache)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-node@v6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">node-version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;lts/*&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">cache</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;npm&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Install Wrangler (cached)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">npm ci</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy to Cloudflare Workers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">npx wrangler deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">CLOUDFLARE_API_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.CLOUDFLARE_API_TOKEN }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">CLOUDFLARE_ACCOUNT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.CLOUDFLARE_ACCOUNT_ID }}</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Of course you’ll want to change your site info on the first 3 lines. You’ll also need to set two secrets in GitHub: <code>CLOUDFLARE_API_TOKEN</code> and <code>CLOUDFLARE_ACCOUNT_ID</code>.</p>
<p>The latter is available on the right-hand column of the Overview page for your Cloudflare domain.</p>
<p>The API key can be created under your Cloudflare profile by going to <code>API Tokens.</code> You’ll need to add the following permissions:</p>
<ul>
<li><code>&lt;your user&gt;</code> Scripts:Edit</li>
<li>All users - User Details:Read</li>
</ul>
<p>The Action itself does two things:</p>
<ol>
<li>It runs daily at 09:00 CST as well as running on push and running “on demand” in that you can manually trigger it from the “Actions” page of your GitHub repository.</li>
<li>It builds your site, but it also caches the build which can make it <em>much</em> faster. On my little site it knocks the build time from ~5 minutes to around 40 seconds, mostly due to all the image processing I do.</li>
<li>It installs Cloudflare’s Wrangler and deploys it per our Wrangler script above.</li>
</ol>
<p>To use Wrangler in this fashion, you&rsquo;ll need to install it in your Hugo site;s repo with npm. Use the following in your repo’s directory to install it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm i -D wrangler@latest</span></span></code></pre></div><p>Note that Wrangler is only needed for deployment. Wrangler is saved as  dev script so it shouldn’t affect anything else on your site.</p>
<p>That’s it. With those two files deployed to your site and Wrangler in your package.json file your Hugo site should be fast and available on Cloudflare Workers.</p>
 ]]></content:encoded></item><item><title>I’m Ramping Up</title><enclosure url="https://chriswiegman.com/images/2026/01/im-ramping-up_hu_bd14f4a5404bff16.jpg" length="1046160" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/im-ramping-up/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/im-ramping-up/</guid><dc:creator>Chris Wiegman</dc:creator><category>Personal</category><category>Reflection</category><pubDate>Fri, 09 Jan 2026 08:00:00 -0600</pubDate><description/><content:encoded><![CDATA[ <p>This week has been spent ramping up.</p>
<p>I’m ramping up at <a href="https://fingerprint.com/">work</a> as we come out of the holiday season and work towards a plan for our new fiscal year that starts on 1 February.</p>
<p>I’m ramping up on this site as I work towards fix issues and tune all manner of things after <a href="https://chriswiegman.com/2025/12/a-hopefully-final-move-back-to-hugo/">moving it back to Hugo</a> a few weeks ago.</p>
<p>I’m ramping up on other side projects while slowly trying to learn Swift and associated techs.</p>
<p>I’m ramping up on family and friend events as we plan trips, events and other activities for 2026.</p>
<p>It wasn’t that long ago that <a href="https://chriswiegman.com/2024/01/weekdays-are-hard/">I didn’t have the energy</a> to ramp up on anything. Getting past that point took a lot of hard work and I couldn’t be happier that it’s all starting to pay off.</p>
 ]]></content:encoded></item><item><title>My Tech Career Has Now Been Longer Than My Aviation Career</title><enclosure url="https://chriswiegman.com/images/2026/01/aviation-to-tech_hu_f3e058260c61d16d.jpg" length="700525" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/my-tech-career-has-now-been-longer-than-my-aviation-career/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/my-tech-career-has-now-been-longer-than-my-aviation-career/</guid><dc:creator>Chris Wiegman</dc:creator><category>Personal</category><category>Career</category><category>Reflection</category><pubDate>Wed, 07 Jan 2026 08:00:00 -0600</pubDate><description>Reflections on my tech career as it is officially older than my aviation career was.</description><content:encoded><![CDATA[ <p>I left my last job in aviation 14 years ago this week.</p>
<p>While I wasn’t technically a pilot for the last 5 years I worked in aviation, I did support the <a href="https://aviation.siu.edu">aviation department at Southern Illinois University Carbondale</a> with tasks such as maintaining flight simulators and even flying planes for maintenance checks when needed. In many ways I miss that job but I was there to get a degree and move into a developer role so, as soon as I graduated, that’s exactly what I did.</p>
<p>I left SIU to work for <a href="https://www.stedwards.edu">St. Edward’s University</a> in Austin, TX where I also received my first official title as  “developer.” Since then I’ve had a number of tech jobs and moved into management. Life looks as different from 2012 to today as it did from when I first started in aviation in 1998 until I left it in 2012.</p>
<p>It’s hard to believe it’s been that long. I still feel like I’m just getting started in tech and I’m not even half way through it. I have 20 years to go before I can consider retirement.</p>
<p>I don’t know how the rest of my career in tech will look but I’m looking forward to see what I can bring to the table for the next 14 years and beyond.</p>
 ]]></content:encoded></item><item><title>Ongoing Feed Work -- My Apologies</title><enclosure url="https://chriswiegman.com/images/2026/01/ongoing-feed-work_hu_53bbcf32f0aeccba.jpg" length="380492" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/ongoing-feed-work--my-apologies/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/ongoing-feed-work--my-apologies/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Web Development</category><category>Digital Life</category><pubDate>Tue, 06 Jan 2026 14:27:13 -0600</pubDate><description/><content:encoded><![CDATA[ <p>If you&rsquo;re seeing this post or others multiple times in your RSS feed, my apologies.</p>
<p>I&rsquo;m trying to do some cleanup and optimization to my RSS template and I&rsquo;m afraid it&rsquo;s probably triggered more than one feed reader. I should be done with this shortly.</p>
 ]]></content:encoded></item><item><title>My 2026 Hugo Workflow</title><enclosure url="https://chriswiegman.com/images/2026/01/editing-in-vs-code_hu_b6902217bcab00da.jpg" length="403783" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/my-2026-hugo-workflow/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/my-2026-hugo-workflow/</guid><dc:creator>Chris Wiegman</dc:creator><category>Technology</category><category>Blogging</category><category>Web Development</category><pubDate>Mon, 05 Jan 2026 08:00:00 -0600</pubDate><description>A high-level overview of how I publish post content to Hugo</description><content:encoded><![CDATA[ <p>While I’m still dialing a few things in, particularly trying to reduce the friction even more on my iPad, I’m quite comfortable with the writing workflow I have for this site at this point. This post will be the first in a series of posts I’m working on to share how I make it work.</p>
<h2 id="a-high-level-overview">A high-level overview</h2>
<p>I had a few major friction points the last time I used <a href="https://gohugo.io">Hugo</a>. The first was trying to manage content. I had all the posts in a single directory and it just wasn’t usable. Second was properly handling drafts. I accidentally published a draft on more than one occasion and I don’t want to do that again. Finally was handling post dates, particularly with drafts.</p>
<p>Today I solve the first issue by using a different post structure. Instead of storing all 800 plus files in a single folder, I put all my posts in <code>/content/posts/&lt;year&gt;/</code> folders. Adding the year folders makes it so much easier for me to sort through the old post list and find what I’m looking for when I&rsquo;m trying to link back to it.</p>
<p>I&rsquo;ve also improved how I name the draft files themselves. In the year folder files are named in the following format: <code>MM-DD-&lt;post title&gt;.md</code>. The date is based on the publish date they appear on the site and makes it even easier for me to find the content I was looking for. Previously I would sometimes use a date, but it was usually when I started a post draft. This could get very confusing when I was trying to find an old post.</p>
<p>Of course, a good naming convention actually has to be followed. To do this today I use two scripts that can be run in any number of ways: “Create Draft” and “Promote Draft.”</p>
<h2 id="working-with-drafts">Working with drafts</h2>
<figure>
    <img class="with-caption" width="1272" height="236" src="https://chriswiegman.com/images/2026/01/select-task.png" alt="A screenshot of the Task Selection in VS Code" decoding="async" loading="lazy" srcset="https://chriswiegman.com/images/2026/01/select-task_hu_2cef91109a7b341a.webp 850w,https://chriswiegman.com/images/2026/01/select-task_hu_5edab9a36ed365cf.webp 710w,https://chriswiegman.com/images/2026/01/select-task_hu_de3fb768bc1ed1d9.webp 300w" sizes="(max-width: 300px) 300w, (max-width: 710px) 710w, 850w" /><figcaption>A screenshot of the Task Selection in VS Code.</figcaption></figure><p>Create Draft does what it says. It creates a new post draft under <code>/content/drafts/</code> and labels the file with a timestamp. This lets me work on the draft for as long as I want without risking publishing it. The file name doesn’t matter either, I can change it to anything I want to help make sorting multiple drafts easier.</p>
<p>As for the draft itself, it uses most of the normal <a href="https://gohugo.io/content-management/front-matter/">front matter</a> but with a few twists. First, it adds the base of an image for the featured image. Next it also adds empty taxonomy terms for both category and tags. Finally, it doesn’t add a date at all as I don’t necessarily know when I’ll publish the post.</p>
<p>To launch the Create Draft script I can either run it from a VS Code task or run it as a GitHub Action. The former works great when I’m on my laptop. The latter I use when I’m working from my iPad.</p>
<figure>
    <img class="with-caption" width="1292" height="778" src="https://chriswiegman.com/images/2026/01/a-fresh-draft.png" alt="A screenshot of a post draft created by script" decoding="async" loading="lazy" srcset="https://chriswiegman.com/images/2026/01/a-fresh-draft_hu_d0e43ed026699d64.webp 850w,https://chriswiegman.com/images/2026/01/a-fresh-draft_hu_d85757578e824e20.webp 710w,https://chriswiegman.com/images/2026/01/a-fresh-draft_hu_77c54a8a5ada1275.webp 300w" sizes="(max-width: 300px) 300w, (max-width: 710px) 710w, 850w" /><figcaption>A screenshot of a post draft in VS Code.</figcaption></figure><h2 id="publishing-new-content">Publishing new content</h2>
<p>When a draft is ready I run the &ldquo;Promote Draft&rdquo; script either from VS Code’s task runner or GitHub Actions. It asks me for a few fields: “now” or “later”, the post file name and a date.</p>
<p>If I only have one post the post file name field doesn&rsquo;t matter as it will select the only post. If there are multiple posts and I forget to specify the filename it will fail with a robust error messages that even lists all the drafts in the folder. I rarely have more than a couple of drafts I&rsquo;m working on at a given time so this make publishing a given post very easy.</p>
<p>If I want the post to appear on my site immediately, I select &ldquo;now&rdquo; and run the script. It will move the post to the appropriate post folder, rename it to conform to my post naming structure, add a timestamp to the <code>date</code> front matter and set the <code>draft</code> flag to false. If I run it through GitHub Actions it will even handle the post commit and if I run it via VS Code I just commit the changes when the script is done.</p>
<p>If I don’t want to publish the post right that moment I can specify “Later” when running the script and enter the date I want it to publish on in YYYY-MM-DD format. It will do all the same as above only it will set the date and time in the file name and in the <code>date</code> front matter to 8:00 am in my home timezone on the date I specified.</p>
<p>When “now” is selected via GitHub Action it will automatically trigger my deployment action to send it to <a href="https://www.cloudflare.com/products/workers/">CloudFlare</a>. If I’ve elected to publish it later the 8:00 am is key as I’ve set my deployment script to also run on a daily schedule at 9:00 am in my home timezone. This will catch the future post date appropriately and publish it to my site, all while accounting for the fact that scheduled GitHub actions aren&rsquo;t perfectly timed.</p>
<p>As a bonus, when new content is published an additional GitHub Action is kicked off to notify various services of the new content, kinda like Ping-o-matic does in WordPress.</p>
<h2 id="issues-i-still-need-to-work-on">Issues I still need to work on</h2>
<p>For the most part, this works great. I can write from <a href="https://ia.net/writer">IA Writer</a>, my app of choice on my iPad, or <a href="https://code.visualstudio.com/">VS Code</a> on my Mac and no my post will be available on my site when I want it to be. This is already a lot better than my workflow the last time I tried to use Hugo.</p>
<p>Where I still have <em>some</em> friction is with the iPad. I cannot preview a post so if I get anything wrong, like an image path, I’ll be in trouble. Second, I have to manually kick off the actions on GitHub’s website, update the repo in <a href="https://workingcopy.app">Working Copy</a> and then remember to sync it all later. It’s not <em>bad</em> but I think I can do better and I’m still experimenting with ways to make that happen.</p>
<p>Finally, editing pages still isn’t great with this workflow (nor was it good with WordPress either). I’m also working on a way to draft edits to pages and have them automatically publish themselves when I want them to.</p>
<p>In the end, though, this is a sustainable workflow I can live with. Content is easier to find and organize, drafting posts is less error-prone and posts are published when I want them to be without me having to worry about publishing anything by accident or missing a post entirely. That’s a huge win and I’m excited for what it will enable for me going forward.</p>
<h2 id="so-how-do-these-scripts-work">So how do these scripts work?</h2>
<p>I’ll follow-up in the coming weeks with posts on each of the scripts, create draft and publish, as well as the VS Code tasks, GitHub Actions and other automation I’m using to make this all work.</p>
 ]]></content:encoded></item><item><title>I’m going to embrace 2026</title><enclosure url="https://chriswiegman.com/images/2026/01/im-going-to-embrace-2026_hu_d945ab5bf1fe2cd7.jpeg" length="3378049" type="image/jpeg"/><link>https://chriswiegman.com/2026/01/im-going-to-embrace-2026/</link><guid isPermaLink="true">https://chriswiegman.com/2026/01/im-going-to-embrace-2026/</guid><dc:creator>Chris Wiegman</dc:creator><category>Personal</category><category>Reflection</category><pubDate>Fri, 02 Jan 2026 08:00:00 -0600</pubDate><description>My theme for 2026 is going to be to embrace it. Here’s what that means to me both personally and professionally.</description><content:encoded><![CDATA[ <p>I remember 2025 like it was just the other day.</p>
<p>&hellip;okay, I won’t try any more jokes.</p>
<p>As <a href="https://chriswiegman.com/2025/12/2025-in-hindsight/">I wrote the other day</a>, last year was defined by my work. This year I have no intention of repeating that.</p>
<p>That’s not to say that I don’t enjoy my work or that I don’t wish to move my career forward. I very much would like to do those things. What I am saying is that I want to do a lot more than just my day job and I’m excited that this year it all looks possible.</p>
<h2 id="a-running-theme-on-these-posts">A Running theme on these posts</h2>
<p>There is a running theme for every new year that I will continue to carry over into 2026:</p>
<p>I want to work on my health. 2025 was not a good year for my fitness or diet. This year I want to get back on track to at least the level I was at when we arrived in Chicago. This means losing 15 pounds, reducing how much we dine out and drink, and getting my activity level back up to something I’m not embarrassed about.</p>
<p>Honestly, these should be doable. The biggest obstacle to my health was my last job and I’ve already started to get better since leaving in August. The only parts that haven’t started to recover are my fitness level and my weight, both of which are tied together and both of which have continued to falter mostly because I managed to get sick more than once this past fall.</p>
<p>I’ve got this.</p>
<h2 id="im-excited-for-new-projects">I’m excited for new projects</h2>
<p>For years I’ve struggled to find tech to work on that is fun for me. I think I failed at this mostly because I kept trying to do things in WordPress. I’m not going to make that mistake anymore.</p>
<p>I’m slowly starting to learn Swift and SwiftUI and have a few project ideas in mind. I’ve also got a lot of things I want to do with this site, from new post ideas to a new theme. Add that to some general utilities I’m working on for myself and I think 2026 will be the first year in many where I’m genuinely excited about doing something in tech again.</p>
<h2 id="im-comfortable-with-the-tech-i-have">I’m comfortable with the tech I have</h2>
<p>Part of the excitement of new tech to learn is I’ve stopped agonizing on my tools and have embraced the tech I have, currently all Apple devices. I still have a <a href="https://frame.work">Framework</a> laptop that can run Linux, but’s it’s mostly been put away in a drawer. I’m done trying to force Linux into my workflow.</p>
<p>Since I shelved the Linux laptop, sometime around August, I’ve found new ways to work with my iPad and my MacBook Pro. It’s amazing what a machine can do when you stop spending all your time trying to make something else work even though something “good enough” is already on your desk.</p>
<p>That’s not to say I’ll never work with a Linux machine again. I did enjoy it. It’s simply an acknowledgement of the fact that Linux doesn’t work for me right now and that’s OK.</p>
<p>All tech is flawed. Instead of trying to fix that I’m going to embrace what I have that works for me today and, honestly, doing so has been fun. I even find myself browsing sites like <a href="https://www.macrumors.com">MacRumors</a> more often and looking forward to new features and hardware. I hadn’t let myself do that in many years.</p>
<h2 id="were-going-to-get-out-more">We’re going to get out more</h2>
<p>Outside of projects to work on, I’m also excited for the prospect of traveling again this year.</p>
<p>The last <em>real</em> trip I took was a work trip to Limerick in February 2024. From that time until we spent a weekend in Carbondale this past October the only “travel” Joy and I did was to either my Dad’s or her sister’s house. This year we’re going to fix that.</p>
<p>Next month I’ll go to Athens for the first time for a work trip. At the end of that, Joy and I will meet in Rome for a week to explore and unwind. I can’t wait.</p>
<p>Later in the year we’re talking about a trip to Germany as well as road trips to finally visit friends we haven’t seen in far too long. I’ll take vacation for some of it (especially the trips to Europe) but I’m going to try to take advantage of my remote status and work wherever we land.</p>
<p>So much of our travel in the past was for a reason. Either work, a wedding or family would give us the reason to visit a place and we’ve been horrible at traveling for anything else. This year we’re going to change that and I can’t wait.</p>
<h2 id="i-need-to-stop-doomscrolling">I need to stop doomscrolling</h2>
<p>Finally, if there’s a real resolution for this year it will be to stop doomscrolling.</p>
<p>Almost every night I find myself sitting down with my iPad after work and, rather than doing anything productive, doomscrolling until it’s time to go to bed. That has to stop.</p>
<p>From reading, to writing, to side projects I’ve tended to replace them all with mindless scrolling. I get why, it was easier when I didn’t know what I wanted to work on, but that’s no longer the case.</p>
<p>Today I have a lot of things I’m actually excited to learn and work on. I don’t need to scroll on here to mask that anymore. That prospect alone is exciting to me.</p>
<h2 id="2026-is-here-im-going-to-embrace-it">2026 is here. I’m going to embrace it</h2>
<p>All this is to say that I’m excited for 2026, both the good and the bad that will come of it.</p>
<p>For the first time in a very long time I’m in a place that feels like home with family and friends nearby. I’m in a job I love. We have a chance to travel again and I’m genuinely excited for a chance to work with new and exciting tech.</p>
<p>I’m hopeful this will all come together for a 2026 worth embracing, even when things don’t work out as planned.</p>
 ]]></content:encoded></item><item><title>2025 in Hindsight</title><enclosure url="https://chriswiegman.com/images/2025/12/2025-in-hindsight_hu_57469881b77b1085.jpeg" length="374364" type="image/jpeg"/><link>https://chriswiegman.com/2025/12/2025-in-hindsight/</link><guid isPermaLink="true">https://chriswiegman.com/2025/12/2025-in-hindsight/</guid><dc:creator>Chris Wiegman</dc:creator><category>Personal</category><category>Reflection</category><pubDate>Wed, 31 Dec 2025 08:00:00 -0600</pubDate><description/><content:encoded><![CDATA[ <p>Here we are, the end of yet another year.</p>
<p>2025 was defined by work. It was interesting for me in that I took what many would consider to be a demotion but was, in fact, one of the better career moves I’ve made.</p>
<p>From January through July we really didn’t do much. My old job meant I could make no plans and I learned the hard way when I tried to do so. We missed concerts, games and other events we had bought tickets for and I didn’t even try booking travel. Instead the first 7 months of the year were filled with me just trying to keep my head above water.</p>
<p>In August things turned around. I took a new job with <a href="https://fingerprint.com">Fingerprint</a> that came out of a conversation I had with Dav Glass, a friend I had first met way back when I was in grad school at SIU. He needed an engineering manager for a remote team at a company that had a product and knows what it wants to do with it.</p>
<p>At first I was a bit torn on whether to make the jump from VP to EM, no matter how bad my quality of life was, but in the end I went for it. Now I’m managing two teams as I try to build them both back up from shakeups that predate my tenure. I know my place. I know what I need to do and this time I have supportive management to make sure we can do it. I’m loving it.</p>
<p>Maybe one day I’ll look at an executive role again. I’m not against it. This EM role, however, is perfect for me right now. What I’ve learned through both companies is what I’m capable of and that I can perform well at the VP level or at the level of a manager and that’s been good for me. WP Engine gave my team so little support that I left there questioning my own abilities. SuperFile and Fingerprint have erased those doubts and now, at Fingerprint, I can push forward in the type of role I’ve been looking to do for a very long time.</p>
<p>With the new job it means we can actually plan things again and we started taking advantage of that in October with our first trip back to Carbondale since 2019. Oh how I miss that town. We were able to see a football game, walk around the campus and see friends we haven’t seen in years, including Mike Veselka who just happened to be in town that weekend himself.</p>
<p>It was great to get away. Going forward we have even more trips planned, but that’s for another post.</p>
<p>On the project front I started new projects and ended some older ones. The most notable project work is that I’m starting to learn Swift and I’ve completely stopped using WordPress for anything, including this site after once again moving back to WordPress in July and then back to Hugo just a week ago.</p>
<p>With the <a href="https://chriswiegman.com/2025/12/so-long-to-kana-my-last-wordpress-project/">sunsetting of Kana</a> that means I don’t have any active WordPress projects, not sites nor plugins nor anything else. In some ways that is a bit sad to me after spending nearly my whole tech career in WordPress, but it’s time. I <a href="https://chriswiegman.com/2024/10/so-long-wordpress/">left the community behind a year ago</a> and was really only using the software out of nostalgia. That’s just not a good reason to use something that really doesn’t do a very good job for my needs, so moving on is the right move and I’m looking forward to building new things.</p>
<p>I didn’t read as much this year. As I write this I’ve only finished 75 books. I didn’t really get into any TV shows, movies or really any media. Where I did spend a lot of time was listening to podcasts and music. Apple Music tells you how many minutes you’ve listened over the year. My previous record was right around 38,000 minutes. This year, again as I write this, I’m somewhere around 95,000 minutes. Music has been my escape and I’ve loved every one of those minutes.</p>
<p>One area that I haven&rsquo;t done well in this year is my health. I’ve walked and exercised far less, first due to my old job and then due to getting sick for a few weeks twice this fall. Add to that my old job brought in fast food daily for lunch and it took a toll with me again reaching over 200lbs. I’ve stopped the climb since leaving that job, but haven’t yet been able to start losing that again.</p>
<p>On the plus side with my health I have slowed and then stopped drinking again. I had been sober for 2.5 years until my mom passed. Since moving to Chicago I was drinking for roughly 3 months (once a week or so) followed by 3 months of not drinking. At SuperFile that increased to drinking once a week and has almost entirely stopped since switching jobs in August, a trend I really hope to continue for the foreseeable future.</p>
<p>One nice part of not traveling much and working in an office for a year is we have started to make new friends here in Chicago. We’ve found a restaurant and pub (with NA beers) near us that has become our Thursday night haunt and has introduced us to some great people. In addition, I’ve started getting out to conferences and other tech events around the area, even speaking in person at an event for the first time since 2020. That’s definitely been a win for us both.</p>
<p>In the end 2025 has, for me, been a tale of two jobs. That’s lead to a mostly quiet year, but not necessarily a bad one. Of course there are things I want to change (like the reading on the scale), but overall I can’t say it hasn&rsquo;t been a bad year. Sometimes “adequate” is just good enough.</p>
 ]]></content:encoded></item></channel></rss>