<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feed.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="https://limulus.net/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>limulus.net</title>
    <link>https://limulus.net/</link>
    <atom:link href="https://limulus.net/feed.xml" rel="self" type="application/rss+xml" />
    <description>Every article on limulus.net.</description>
    <language>en</language>
    <item>
      <title>Link: Why USA 1.0 failed</title>
      <link>https://limulus.net/links/2026/07/04/why-usa-1-0-failed/</link>
      <description>&lt;p&gt;
  It’s always struck me as a bit weird that we effectively treat Independence Day as the
  USA’s birthday. Sure, it’s an important date in the country’s history, but the signing of
  the Constitution is where our nation really got its start. What came before it is
  unrecognizable to us as our country.
&lt;/p&gt;
&lt;p&gt;
  Any way, today is a good day to watch &lt;a
    href=&quot;https://www.youtube.com/watch?v=nSBbX5gn1Lg&quot;
  &gt;this video&lt;/a&gt; and be reminded that change is possible and that revolutions don’t always
  require blood. Even the Constitution can be amended.
&lt;/p&gt;
</description>
      <pubDate>Sat, 04 Jul 2026 16:40:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/links/2026/07/04/why-usa-1-0-failed/</guid>
    </item>
    <item>
      <title>Link: Is it ethical to use AI?</title>
      <link>https://limulus.net/links/2026/06/28/make-ai-boring-again/</link>
      <description>&lt;p&gt;
  I &lt;a href=&quot;https://limulus.net/feed/better-software-with-ai/#fn2&quot;&gt;previously suggested&lt;/a&gt;
  that I might write about my thoughts on the ethics of generative AI. Now I don’t need to,
  because &lt;a href=&quot;https://charity.wtf/p/make-ai-boring-again&quot;&gt;Charity Majors&lt;/a&gt; covered
  just about everything I wanted to. Of course she did it way better than I could have.
&lt;/p&gt;
&lt;p&gt;I endorse the whole piece, but especially this:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    I am troubled by the amount of motivated reasoning coming from the people I feel
    politically aligned with. It’s very easy to mock and write off people who vocally hate
    AI for a long list of things they never seemed to give a shit about before they realized
    they hated AI and went looking for reasons.
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And oh man does this footnote resonate with me:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    I have to say how much whiplash it gives me, as a child of open source and copyleft, to
    find that copyright law and internet advertising are now… the good guys? this timeline
    is WEIRD you guys
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  It really does continue to astound me how so many people seem to have decided that &lt;a
    href=&quot;https://en.wikipedia.org/wiki/Fair_use&quot;
  &gt;fair use&lt;/a&gt; shouldn’t be a thing anymore. We used to &lt;a
    href=&quot;https://yro.slashdot.org/story/00/10/16/1217205/students-protest-dmca-during-visit-by-valenti&quot;
  &gt;protest&lt;/a&gt; that kind of crap!
&lt;/p&gt;
</description>
      <pubDate>Sun, 28 Jun 2026 18:45:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/links/2026/06/28/make-ai-boring-again/</guid>
    </item>
    <item>
      <title>Using Lima for Coding Agent Isolation</title>
      <link>https://limulus.net/feed/lima-agent-isolation/</link>
      <description>&lt;p&gt;
  A number of months ago, before &lt;a
    href=&quot;https://code.claude.com/docs/en/permission-modes#eliminate-prompts-with-auto-mode&quot;
  &gt;auto mode&lt;/a&gt; was made available, I had started using Claude Code’s &lt;code
  &gt;--dangerously-skip-permissions&lt;/code&gt; flag (YOLO mode). Up until then I had been using
  &lt;code&gt;claude&lt;/code&gt;’s &lt;a href=&quot;https://code.claude.com/docs/en/sandboxing&quot;&gt;sandbox&lt;/a&gt;,
  but it has (or maybe had) a number of annoying issues with things like
  &lt;code&gt;git&lt;/code&gt; on macOS. So I decided I would try YOLO mode, but to mitigate the blast
  radius by only running it in a &lt;a href=&quot;https://containers.dev/&quot;&gt;dev container&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
  I’d dabbled with dev containers previously, even using &lt;a
    href=&quot;https://github.com/features/codespaces&quot;
  &gt;GitHub Codespaces&lt;/a&gt; until I discovered a way to effectively have &lt;a
    href=&quot;https://limulus.net/tils/git/private-branches-in-public-repos/&quot;
  &gt;private WIP branches&lt;/a&gt;. Local dev containers are nicer than having to pay for a slow
  cloud instance, but they are also annoying. I never established a good mental model of
  what VS Code plugins ran in the host or container and why some would not be automatically
  reinstalled after a container rebuild. One of the sillier annoyances is how there seems to
  be no simple way to get a container to adopt the host’s timezone. Then there’s the fact
  that the standard dev-container approach means having a container for each project, which
  seems like an annoying amount of duplicate configuration. And somehow containers running
  in Docker also just seemed slower than they ought to be.
&lt;/p&gt;
&lt;p&gt;
  So eventually I just stopped using them, and started running &lt;code&gt;claude&lt;/code&gt; as my
  user directly on my Mac, relying on auto mode’s protections. Then a slightly unsettling
  thing happened with Claude Opus 4.8.
&lt;/p&gt;
&lt;p&gt;
  I had an item in the project’s &lt;code&gt;BACKLOG.md&lt;/code&gt; to enable branch protection on the
  &lt;code&gt;main&lt;/code&gt; branch to keep an agent in YOLO mode from pushing to &lt;code&gt;main&lt;/code&gt;
  and triggering a release. I had
  &lt;code&gt;claude&lt;/code&gt; in auto mode start working on this. Even though the backlog item
  spelled out why the protection should exist, &lt;code&gt;claude&lt;/code&gt; nevertheless used the
  GitHub client &lt;code&gt;gh&lt;/code&gt; command with my credentials to get around the branch
  protection on &lt;code&gt;main&lt;/code&gt; and merge its own commits!
&lt;/p&gt;
&lt;p&gt;
  The irony of this was too much for me to bear, so I decided I really needed to make sure
  &lt;code&gt;claude&lt;/code&gt; was properly isolated from commands that it can use with my
  credentials. Now, to be fair, my git credentials in my dev containers are just as readily
  available as they are on the host. And while I don’t think I have the &lt;code&gt;gh&lt;/code&gt;
  command set up in any of my containers, it’s not an unreasonable thing to have set up
  either. What I really needed was an environment that — aside from Claude Code itself —
  does not have access to my credentials for any service.
&lt;/p&gt;
&lt;p&gt;
  This led me to do a bit of research, where I learned about &lt;a href=&quot;https://lima-vm.io/&quot;
  &gt;Lima&lt;/a&gt;. It manages and runs Linux virtual machines that, on macOS, feel to me like a
  lightweight Docker. I’ve started using a single Lima VM to run &lt;code&gt;claude&lt;/code&gt; in,
  mounting my entire &lt;code&gt;~/Developer&lt;/code&gt;
  directory&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a
      href=&quot;https://limulus.net/feed/lima-agent-isolation/#fn1&quot;
      id=&quot;fnref1&quot;
    &gt;[1]&lt;/a&gt;&lt;/sup&gt; and &lt;code&gt;~/.agents/skills&lt;/code&gt; directory. So long as I avoid putting
  credentials into this VM, it’s considerably safer to use Claude Code in auto mode from
  within it.
&lt;/p&gt;
&lt;p&gt;
  This setup addresses many of my gripes with using dev containers. It requires only one VM
  and configuration for all my projects, inherits the timezone of the host, and feels faster
  than Docker-based dev containers.
&lt;/p&gt;
&lt;p&gt;
  I’ve done a few things to the &lt;a
    href=&quot;https://github.com/limulus/dotfiles/blob/ec7d68d8e4a2c1036e2f7a82fa9bf452c453bc3f/.config/lima/cochineal.yaml&quot;
  &gt;Lima configuration&lt;/a&gt;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a
      href=&quot;https://limulus.net/feed/lima-agent-isolation/#fn2&quot;
      id=&quot;fnref2&quot;
    &gt;[2]&lt;/a&gt;&lt;/sup&gt; that help make this setup have as little friction as possible. First, I
  have the VM setup script install
  &lt;a href=&quot;https://github.com/limulus/dotfiles/tree/main&quot;&gt;my dotfiles&lt;/a&gt;. I also have two
  custom zsh commands in the VM that help with
  &lt;code&gt;node_modules&lt;/code&gt; isolation: &lt;code&gt;nmlocal&lt;/code&gt; binds &lt;code&gt;node_modules&lt;/code&gt;
  to VM-local storage and
  &lt;code&gt;nmstatus&lt;/code&gt; lists those active mounts. This avoids issues with sharing &lt;code
  &gt;node_modules&lt;/code&gt; between the host and guest platforms, though there’s some cognitive
  overhead with needing to maintain this. Finally, on the host, I added a &lt;a
    href=&quot;https://github.com/limulus/dotfiles/blob/ec7d68d8e4a2c1036e2f7a82fa9bf452c453bc3f/.zshrc#L71-L81&quot;
  &gt;zsh function&lt;/a&gt; for &lt;code&gt;limactl shell&lt;/code&gt; that, when run inside a directory in
  &lt;code&gt;~/Developer&lt;/code&gt;, puts me in the matching directory in the VM.
&lt;/p&gt;
&lt;p&gt;
  Lima auto-forwards ports onto the host, similar to dev containers. This can be convenient
  for things like &lt;a href=&quot;https://github.com/yoshiko-pg/difit&quot;&gt;difit&lt;/a&gt;, but there’s a
  rough edge for things like dev servers. If your dev server watches files for changes, and
  you make edits using an editor running on the host, you’ll realize that file system events
  are not propagated from the host to the VM. However, this does work the other way around
  — a change to a file made in the VM does trigger file system events on the host. You can
  configure file watchers like &lt;a href=&quot;https://github.com/paulmillr/chokidar&quot;&gt;Chokidar&lt;/a&gt;
  to poll the filesystem for changes, but just running dev servers from the host is probably
  a reasonable solution.
&lt;/p&gt;
&lt;p&gt;
  It won’t surprise me if Anthropic soon ships a feature to use a local VM built into Claude
  Code itself. (&lt;a href=&quot;https://claude.com/product/cowork&quot;&gt;Claude Cowork&lt;/a&gt; actually
  already uses one for executing commands.) It’s ultimately still an awkward solution, but
  it’s what we have until macOS and other desktop operating systems get better APIs to
  ensure that processes spawned by agents do not automatically have the same access to the
  user’s credentials that user-spawned commands do.
&lt;/p&gt;
&lt;section class=&quot;footnotes section-separator&quot;&gt;
  &lt;ol class=&quot;footnotes-list&quot;&gt;
    &lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;
      &lt;p&gt;
        If you didn’t know, &lt;code&gt;~/Developer&lt;/code&gt; has long been the canonical location
        for your software projects on macOS. It even gets its own special &lt;a
          href=&quot;https://www.cocoadelica.co.uk/blog/default-folder-icon-in-macos&quot;
        &gt;icon&lt;/a&gt; in the Finder. &lt;a
          href=&quot;https://limulus.net/feed/lima-agent-isolation/#fnref1&quot;
          class=&quot;footnote-backref&quot;
        &gt;↩︎&lt;/a&gt;
      &lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;
      &lt;p&gt;
        You might notice that the VM’s name is &lt;a
          href=&quot;https://en.wikipedia.org/wiki/Cochineal&quot;
        &gt;cochineal&lt;/a&gt;. My host naming scheme has been Sonoran Desert flora for quite some
        time, so it seems fitting to use the name of an insect that lives on prickly pears
        as a VM name. &lt;a
          href=&quot;https://limulus.net/feed/lima-agent-isolation/#fnref2&quot;
          class=&quot;footnote-backref&quot;
        &gt;↩︎&lt;/a&gt;
      &lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Fri, 19 Jun 2026 22:28:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/feed/lima-agent-isolation/</guid>
    </item>
    <item>
      <title>Link: Eight years of wanting, three months of building with AI</title>
      <link>https://limulus.net/links/2026/04/07/eight-years-wanting-building-ai/</link>
      <description>&lt;p&gt;
  Lalit Maganti wrote a &lt;a href=&quot;https://lalitm.com/post/building-syntaqlite-ai/&quot;
  &gt;remarkable account&lt;/a&gt; of building &lt;a href=&quot;https://lalitm.com/post/syntaqlite/&quot;
  &gt;syntaqlite&lt;/a&gt; using Claude Code:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    If you’re using AI to generate code at industrial scale, you &lt;em&gt;have&lt;/em&gt; to refactor
    constantly and continuously. If you don’t, things immediately get out of hand. This was
    the central lesson of the vibe-coding month: I didn’t refactor enough, the codebase
    became something I couldn’t reason about, and I had to throw it all away. In the
    rewrite, refactoring became the core of my workflow. After every large batch of
    generated code, I’d step back and ask “is this ugly?” Sometimes AI could clean it up.
    Other times there was a large-scale abstraction that AI couldn’t see but I could; I’d
    give it the direction and let it execute.
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  The whole post is worth reading. With the project I have been working on I have been
  having similar experiences. Especially the need to always prioritize refactoring.
&lt;/p&gt;
&lt;p&gt;
  My current project recently reached the point where it was getting hard to reason about
  areas of responsibility, so I decided it needed to be refactored into a monorepo. In
  non-AI time, this is something I would have sat on for a while longer before begrudgingly
  taking it on. But in AI-time, delaying this refactor would mean compounded deterioration
  of my understanding of how the pieces of the codebase are interconnected.
&lt;/p&gt;
&lt;p&gt;
  Currently, I’m keeping a &lt;code&gt;BACKLOG.md&lt;/code&gt; file in my project. It’s just a handful
  of sections with bullet lists of to-dos. Right at the top is a section for refactors. If I
  think of a refactor it becomes the very next thing to work on. I have yet to regret doing
  a refactor before doing more feature work.
&lt;/p&gt;
&lt;p&gt;
  Via &lt;a href=&quot;https://simonwillison.net/2026/Apr/5/building-with-ai/&quot;
  &gt;simonwillison.net&lt;/a&gt;.
&lt;/p&gt;
</description>
      <pubDate>Wed, 08 Apr 2026 04:10:01 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/links/2026/04/07/eight-years-wanting-building-ai/</guid>
    </item>
    <item>
      <title>Choosing to Make Better Software With AI</title>
      <link>https://limulus.net/feed/better-software-with-ai/</link>
      <description>&lt;p&gt;With some notable exceptions&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://limulus.net/feed/better-software-with-ai/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, there’s something important missing from what I read
about AI and how it’s affecting software engineering: the enormous opportunity that &lt;a href=&quot;https://simonwillison.net/guides/agentic-engineering-patterns/what-is-agentic-engineering/&quot;&gt;agentic
engineering&lt;/a&gt; is giving us to significantly improve software quality.&lt;/p&gt;
&lt;p&gt;If this sounds completely bonkers to you — keep reading. My goal with this piece is to help
you understand why I think this is true.&lt;/p&gt;
&lt;p&gt;If this makes you say “well of course” — keep reading. My goal is to help you communicate
this idea to our rightfully skeptical peers. I think we may be intuiting something that is
not obvious to everyone.&lt;/p&gt;
&lt;p&gt;If you have other reservations about AI — I’m not addressing them here.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://limulus.net/feed/better-software-with-ai/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; But
reading this may help you understand why so many software engineers are adopting agentic
engineering despite qualms they may share with you.&lt;/p&gt;
&lt;h2 id=&quot;the-contradiction&quot; tabindex=&quot;-1&quot;&gt;The Contradiction&lt;/h2&gt;
&lt;p&gt;It seems impossible: how can generative AI — so well known for generating low quality output
that everyone agrees to call it “slop” — actually produce quality software? Even when coding
agents iterate on writing code, surely the end result would only get worse with each pass.&lt;/p&gt;
&lt;p&gt;And it can be like this. In the hands of someone who is ignorant to what quality software
looks like, the end result will be slop. Like a house where the owner adds room after room
with no architect, no structural engineer, further changes become increasingly dangerous.&lt;/p&gt;
&lt;article-photo id=&quot;through-the-cone-by-jaroslava-brychtova-and-stanislav-libensky&quot; placement=&quot;right&quot; class=&quot;wdam8yees&quot;&gt;
  &lt;figure&gt;
    &lt;a href=&quot;https://limulus.net/photos/through-the-cone-by-jaroslava-brychtova-and-stanislav-libensky/&quot;&gt;&lt;pho-picture placement=&quot;right&quot; class=&quot;wdam8yees&quot;&gt;
  &lt;blur-hash blurhash=&quot;LKIhvyWV~qRjo}ofD%Rj~pM{M{xu&quot; width=&quot;3000&quot; height=&quot;2000&quot;&gt;
        &lt;picture&gt;
          &lt;source srcset=&quot;https://pho.limulus.net/TcgEL9hO/lg.WxOU7Uze.avif 3000w, https://pho.limulus.net/TcgEL9hO/md.UsRMdUPI.avif 1500w&quot; type=&quot;image/avif&quot; width=&quot;3000&quot; height=&quot;2000&quot;&gt;
          &lt;source srcset=&quot;https://pho.limulus.net/TcgEL9hO/lg.fxRymkOS.jpeg 3000w, https://pho.limulus.net/TcgEL9hO/md.suCu_HiZ.jpeg 1500w&quot; type=&quot;image/jpeg&quot; width=&quot;3000&quot; height=&quot;2000&quot;&gt;
          &lt;img async=&quot;&quot; crossorigin=&quot;anonymous&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://pho.limulus.net/TcgEL9hO/lg.fxRymkOS.jpeg&quot; srcset=&quot;https://pho.limulus.net/TcgEL9hO/lg.fxRymkOS.jpeg 3000w, https://pho.limulus.net/TcgEL9hO/md.suCu_HiZ.jpeg 1500w&quot; alt=&quot;A large, translucent dark gray cast glass sculpture titled &quot; through=&quot;&quot; the=&quot;&quot; cone&quot;=&quot;&quot; by=&quot;&quot; jaroslava=&quot;&quot; brychtová=&quot;&quot; and=&quot;&quot; stanislav=&quot;&quot; libenský.=&quot;&quot; piece=&quot;&quot; takes=&quot;&quot; form=&quot;&quot; of=&quot;&quot; a=&quot;&quot; cone=&quot;&quot; that=&quot;&quot; has=&quot;&quot; been=&quot;&quot; sliced=&quot;&quot; open=&quot;&quot; with=&quot;&quot; sweeping=&quot;&quot; vertical=&quot;&quot; cut,=&quot;&quot; creating=&quot;&quot; two=&quot;&quot; sharp=&quot;&quot; triangular=&quot;&quot; planes=&quot;&quot; lean=&quot;&quot; apart=&quot;&quot; to=&quot;&quot; reveal=&quot;&quot; smooth,=&quot;&quot; curved=&quot;&quot; hollow=&quot;&quot; interior.=&quot;&quot; glass=&quot;&quot; transitions=&quot;&quot; from=&quot;&quot; nearly=&quot;&quot; opaque=&quot;&quot; at=&quot;&quot; thick=&quot;&quot; edges=&quot;&quot; lighter,=&quot;&quot; more=&quot;&quot; luminous=&quot;&quot; grayGreen=&quot;&quot; where=&quot;&quot; material=&quot;&quot; thins=&quot;&quot; near=&quot;&quot; opening.=&quot;&quot; sculpture=&quot;&quot; sits=&quot;&quot; on=&quot;&quot; white=&quot;&quot; pedestal=&quot;&quot; in=&quot;&quot; gallery=&quot;&quot; setting,=&quot;&quot; hint=&quot;&quot; another=&quot;&quot; darker,=&quot;&quot; multicolored=&quot;&quot; visible=&quot;&quot; right=&quot;&quot; edge=&quot;&quot; frame.&quot;=&quot;&quot; width=&quot;3000&quot; height=&quot;2000&quot; placement=&quot;right&quot; class=&quot;wdam8yees&quot;&gt;
&lt;/picture&gt;
      &lt;/blur-hash&gt;
&lt;p&gt;&lt;/p&gt;&lt;/pho-picture&gt;&lt;/a&gt;
&lt;figcaption&gt;
&lt;span&gt;Through the Cone by Jaroslava  Brychtová and Stanislav Libenský&lt;/span&gt;
&lt;/figcaption&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/figure&gt;
&lt;/article-photo&gt;
&lt;p&gt;But in the hands of someone who cares about quality and knows what it looks like, things can
play out differently.&lt;/p&gt;
&lt;h2 id=&quot;a-baseline-workflow-for-quality&quot; tabindex=&quot;-1&quot;&gt;A Baseline Workflow For Quality&lt;/h2&gt;
&lt;p&gt;Let me set a baseline for what I think reflects the current state of how a software engineer
that cares about quality typically works with agents to make a code change. At least, this
is what I have found myself doing.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Plan the changes with the agent, using the agent’s planning mode.&lt;/li&gt;
&lt;li&gt;Refine the plan with the agent by asking for clarifications, pushing back on assumptions,
resolving ambiguities, discussing rationales, but also being open to approaches the model
suggests.&lt;/li&gt;
&lt;li&gt;Once the plan is acceptable, let the agent loose to edit files and iteratively run
validation until it thinks the change is complete.&lt;/li&gt;
&lt;li&gt;Review the changes, asking for either clarifications or refactors as potential issues
are found. Repeat until it’s good to commit the changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A common lament is that reviewing changes to AI-generated code is tiresome. I think this
manifests in two main ways: lots of changes that superficially look fine but may have subtle
issues; and code that is very wrong and requires a frustrating number of changes. However,
this can be mitigated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using the best (larger/slower) model. Today, that’s Claude Opus 4.6. Sure, some kinds of
straightforward refactors a smaller/faster model will perform fine. But my experience is I
typically regret being stingy with model choice. I find that Opus 4.6 avoids things like
making meaningless test assertions that make review tricky.&lt;/li&gt;
&lt;li&gt;Avoiding AI providers like GitHub Copilot that do per-prompt pricing, which punishes
conversational approaches towards refinement and understanding.&lt;/li&gt;
&lt;li&gt;Iterating on the plan before letting the agent make changes helps prevent too many things
being wrong.&lt;/li&gt;
&lt;li&gt;Demanding 100% test coverage as part of the validation in step 3 ensures every branch gets
a test, which further reduces the chance for the agent to introduce non-functional code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I’m finding with this process is that the refactors I ask for as part of step 4 are
largely about simplification, organization, addressing code smells, or discovering there’s
some unwanted consequence of the implementation I didn’t foresee during planning. These
kinds of problems are far more interesting to find and tackle, making code review less
tiresome and more engaging.&lt;/p&gt;
&lt;h2 id=&quot;going-beyond-the-baseline&quot; tabindex=&quot;-1&quot;&gt;Going Beyond the Baseline&lt;/h2&gt;
&lt;p&gt;If we stopped here I’d say we are getting about the same level of quality software as we’d
get with doing proper Test Driven Development, but with the speed advantage of not having to
type everything out manually. I would argue this alone justifies adopting agentic
engineering, so long as we are keeping our changes small and incremental enough to avoid
accumulating &lt;a href=&quot;https://margaretstorey.com/blog/2026/02/09/cognitive-debt/&quot;&gt;cognitive debt&lt;/a&gt;.&lt;/p&gt;
&lt;article-photo id=&quot;lynx-after-duerer-by-marta-klonowska&quot; placement=&quot;left&quot; class=&quot;wdam8yees&quot;&gt;
  &lt;figure&gt;
    &lt;a href=&quot;https://limulus.net/photos/lynx-after-duerer-by-marta-klonowska/&quot;&gt;&lt;pho-picture placement=&quot;left&quot; class=&quot;wdam8yees&quot;&gt;
  &lt;blur-hash blurhash=&quot;LHJ8bEo0~X%MofM{D$oK?bt7R%IU&quot; width=&quot;769&quot; height=&quot;1280&quot;&gt;
        &lt;picture&gt;
          &lt;source srcset=&quot;https://pho.limulus.net/hUvgc7Bh/lg.aO6yceTB.avif 769w, https://pho.limulus.net/hUvgc7Bh/md.aO6yceTB.avif 769w&quot; type=&quot;image/avif&quot; width=&quot;769&quot; height=&quot;1280&quot;&gt;
          &lt;source srcset=&quot;https://pho.limulus.net/hUvgc7Bh/lg.jYWLeuW2.jpeg 769w, https://pho.limulus.net/hUvgc7Bh/md.jYWLeuW2.jpeg 769w&quot; type=&quot;image/jpeg&quot; width=&quot;769&quot; height=&quot;1280&quot;&gt;
          &lt;img async=&quot;&quot; crossorigin=&quot;anonymous&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://pho.limulus.net/hUvgc7Bh/lg.jYWLeuW2.jpeg&quot; srcset=&quot;https://pho.limulus.net/hUvgc7Bh/lg.jYWLeuW2.jpeg 769w, https://pho.limulus.net/hUvgc7Bh/md.jYWLeuW2.jpeg 769w&quot; alt=&quot;A sculpture of a seated lynx made entirely from thousands of small, pale green-tinted glass shards, giving the animal a spiky, crystalline fur texture. The lynx sits upright on a white pedestal with its tall, tufted ears prominently raised. The translucent glass catches the light, creating a shimmering, frost-like appearance against a plain white background. The work is “Lynx After a Sketchbook Page by Albrecht Dürer” by Marta Klonowska.&quot; width=&quot;769&quot; height=&quot;1280&quot; placement=&quot;left&quot; class=&quot;wdam8yees&quot;&gt;
&lt;/picture&gt;
      &lt;/blur-hash&gt;
&lt;p&gt;&lt;/p&gt;&lt;/pho-picture&gt;&lt;/a&gt;
&lt;figcaption&gt;
&lt;span&gt;Lynx After Dürer by Marta Klonowska&lt;/span&gt;
&lt;/figcaption&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/figure&gt;
&lt;/article-photo&gt;
&lt;p&gt;But we don’t have to stop here! There’s a whole lot more we can now do to improve the
quality of our software with a coding agent’s help.&lt;/p&gt;
&lt;h3 id=&quot;testing-infrastructure&quot; tabindex=&quot;-1&quot;&gt;Testing Infrastructure&lt;/h3&gt;
&lt;p&gt;Have you ever felt like you had no choice but to put a timeout or sleep in a test? You know
it’s bad because this makes tests slow and flaky, but you do it anyway because the
alternative is creating some kind of event system.&lt;/p&gt;
&lt;p&gt;Or have you ever avoided writing integration tests and smoke tests, because staging an
environment they can run in is high effort?&lt;/p&gt;
&lt;p&gt;Have you avoided things like performance tests, automated mutation testing, fuzz testing,
fault injection, static analyzers, and accessibility scanners?&lt;/p&gt;
&lt;p&gt;Well, now you’re probably only a prompt or two away from integrating these things in your
codebase. All of these things got dramatically more accessible with coding agents and they
can have a marked effect on both quality and security.&lt;/p&gt;
&lt;p&gt;Of course, this testing infrastructure is code too, so we do need to consider its quality as
well. It needs to be understandable, fast, and deterministic.&lt;/p&gt;
&lt;h3 id=&quot;documentation&quot; tabindex=&quot;-1&quot;&gt;Documentation&lt;/h3&gt;
&lt;p&gt;Something I have started doing is creating &lt;a href=&quot;https://adr.github.io&quot;&gt;Architecture Decision Records&lt;/a&gt;. These are
documents that detail various decisions about how the software works or is written, and the
rationale for the decisions. These documents come out of either planning sessions or
refactors. Writing these things by hand would be a bore, so I have the agent write them, and
give them a quick proofread.&lt;/p&gt;
&lt;p&gt;This is something I would never bother with if it weren’t for coding agents. Not just
because writing them is a chore, but because I know that no one is going to bother reading
them. But the trick here is that during planning an agent will read the ones relevant to
their prompt! This is the sort of thing that is incredibly helpful to keep a codebase on the
rails.&lt;/p&gt;
&lt;p&gt;Commit messages are another place where I typically let the agent with context for the
changes do the writing for me. It does a good enough job for the value that commit messages
provide. (I find that few people read my commit messages, despite the effort I put into
them.) If your commit messages read like &lt;code&gt;updated reticulator.ts&lt;/code&gt;, leveraging the agent’s
context is an easy win for better communication around your changes.&lt;/p&gt;
&lt;h3 id=&quot;refactors&quot; tabindex=&quot;-1&quot;&gt;Refactors&lt;/h3&gt;
&lt;p&gt;If you’ve got an older codebase, chances are there are lots of large refactors you want to do
but don‘t have the time for. If you have good test coverage, agents can reduce the effort
required and let you refactor sooner.&lt;/p&gt;
&lt;article-photo id=&quot;forest-glass-by-katherine-gray&quot; placement=&quot;right&quot; class=&quot;wdam8yees&quot;&gt;
  &lt;figure&gt;
    &lt;a href=&quot;https://limulus.net/photos/forest-glass-by-katherine-gray/&quot;&gt;&lt;pho-picture placement=&quot;right&quot; class=&quot;wdam8yees&quot;&gt;
  &lt;blur-hash blurhash=&quot;LCJRgbWCt8xuE0WBofof_4ayjsWB&quot; width=&quot;3000&quot; height=&quot;2000&quot;&gt;
        &lt;picture&gt;
          &lt;source srcset=&quot;https://pho.limulus.net/AdTgzjH3/lg.CYDdyrfs.avif 3000w, https://pho.limulus.net/AdTgzjH3/md.99vDQU81.avif 1500w&quot; type=&quot;image/avif&quot; width=&quot;3000&quot; height=&quot;2000&quot;&gt;
          &lt;source srcset=&quot;https://pho.limulus.net/AdTgzjH3/lg.1yJMk_mz.jpeg 3000w, https://pho.limulus.net/AdTgzjH3/md.LERtVQ9O.jpeg 1500w&quot; type=&quot;image/jpeg&quot; width=&quot;3000&quot; height=&quot;2000&quot;&gt;
          &lt;img async=&quot;&quot; crossorigin=&quot;anonymous&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://pho.limulus.net/AdTgzjH3/lg.1yJMk_mz.jpeg&quot; srcset=&quot;https://pho.limulus.net/AdTgzjH3/lg.1yJMk_mz.jpeg 3000w, https://pho.limulus.net/AdTgzjH3/md.LERtVQ9O.jpeg 1500w&quot; alt=&quot;Three tall, tower-like sculptural columns made of hundreds of glass drinking vessels — goblets, tumblers, wine glasses, and cups — arranged on transparent acrylic shelves. Within each tower, the glasses are organized by color to form the shape of a tree: clear and amber-toned pieces at the base suggest a trunk, while green and teal glasses cluster toward the top to form a leafy crown. Each tower rises from a white pedestal behind a low glass barrier, set against a plain white gallery wall. The installation is “Forest Glass” by Katherine Gray.&quot; width=&quot;3000&quot; height=&quot;2000&quot; placement=&quot;right&quot; class=&quot;wdam8yees&quot;&gt;
&lt;/picture&gt;
      &lt;/blur-hash&gt;
&lt;p&gt;&lt;/p&gt;&lt;/pho-picture&gt;&lt;/a&gt;
&lt;figcaption&gt;
&lt;span&gt;Forest Glass by Katherine Gray&lt;/span&gt;
&lt;/figcaption&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/figure&gt;
&lt;/article-photo&gt;
&lt;p&gt;Alas, if it’s an active codebase with more than a few contributors, this won’t help avoid a
large refactor being disruptive. Still, agents can reduce the effort of splitting out
inactive areas of the code into microservices or micro-frontends to allow a refactor to a
portion of the code.&lt;/p&gt;
&lt;h3 id=&quot;mitigating-the-maintenance-burden&quot; tabindex=&quot;-1&quot;&gt;Mitigating the Maintenance Burden&lt;/h3&gt;
&lt;p&gt;A high quality codebase feels heavy. It’s got &lt;em&gt;lots&lt;/em&gt; of tests — probably an order of
magnitude more test code than production code. It’s also got lots of tools to format, lint,
type check, and otherwise validate code. It makes judicious use of libraries to avoid
repeating the mistakes of others. Keeping these tools and libraries up-to-date has
significant maintenance cost. Attempts to automate this work have not been that successful,
but I suspect that this is going to start to change as we find ways to safely integrate
agents into automation, with things like &lt;a href=&quot;https://github.github.com/gh-aw/&quot;&gt;agentic workflows&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;where-this-is-heading&quot; tabindex=&quot;-1&quot;&gt;Where This Is Heading&lt;/h2&gt;
&lt;p&gt;Having been experimenting with agentic engineering for a while now&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://limulus.net/feed/better-software-with-ai/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;, I’ve come
to the conclusion that it gives us the ability to do more to ensure quality — whether that’s
creating tests we would have otherwise skipped or planning changes with a model that can
suggest approaches we may not have otherwise considered. As more people get familiar with
this workflow, I think the common wisdom of using AI in the process of creating software is
likely to flip from “it will result in lower quality software” to “not using it results in
lower quality software.”&lt;/p&gt;
&lt;p&gt;I think that will happen even if LLM capability has already plateaued.&lt;/p&gt;
&lt;p&gt;As the common wisdom changes, more and more folks who have been resistant to adopting this
practice are going to get pushed to adopt it, in spite of their reservations. Being forced
through this kind of change stinks, which is why I’m glad that I’ve been exposing myself to
it.&lt;/p&gt;
&lt;p&gt;For better or for worse, it seems likely that AI is going to happen to all of us. We’ll need
to find a way to be resilient in the face of that change. Using agentic engineering as an
opportunity to allocate more of my energy to improving the reliability, user experience, and
security of my software is how I am trying to do that.&lt;/p&gt;
&lt;section class=&quot;footnotes section-separator&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Except of course Simon Willison &lt;a href=&quot;https://simonwillison.net/guides/agentic-engineering-patterns/better-code/&quot;&gt;wrote something along these lines&lt;/a&gt;
as I was sitting on a first draft. Go read that too!&lt;/p&gt;
&lt;p&gt;It looks like Gergely Orosz, &lt;a href=&quot;https://www.anildash.com/2024/11/19/dont-call-it-a-substack/&quot;&gt;writing for Substack&lt;/a&gt;, may have also &lt;a href=&quot;https://newsletter.pragmaticengineer.com/p/are-ai-agents-actually-slowing-us&quot;&gt;posted about
this&lt;/a&gt;. I have not read the full post, since I cancelled my paid
Substack subscriptions back in 2023, due to their insistence on &lt;a href=&quot;https://arstechnica.com/tech-policy/2025/07/substacks-nazi-problem-wont-go-away-after-push-notification-apology/&quot;&gt;hosting
white-supremacist content&lt;/a&gt;. &lt;a href=&quot;https://limulus.net/feed/better-software-with-ai/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;This is not to say that I have not given a great deal of thought to
environmental and ethical concerns around generative AI. I have. But a lot of the
anti-AI discourse feels to me like moral panic where many claims do not hold up to
scrutiny and a fair assessment does not result in a slam dunk case against it. Maybe I
will write about this some day, but my bandwidth to write long-form is not as wide as I
wish it was.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: I &lt;a href=&quot;https://limulus.net/links/2026/06/28/make-ai-boring-again/&quot;&gt;fully endorse&lt;/a&gt; Charity Majors’ piece on &lt;a href=&quot;https://charity.wtf/2026/06/24/make-ai-boring-again-xpost/&quot;&gt;AI ethics&lt;/a&gt;. &lt;a href=&quot;https://limulus.net/feed/better-software-with-ai/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Last year I built &lt;a href=&quot;https://github.com/limulus/tcx2webvtt&quot;&gt;tcx2webvtt&lt;/a&gt; using Claude Code and what was an early form
of a plan/code/review/refactor loop, using Sonnet 3.7 and 4.0. &lt;a href=&quot;https://limulus.net/feed/better-software-with-ai/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Sat, 04 Apr 2026 17:30:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/feed/better-software-with-ai/</guid>
    </item>
    <item>
      <title>Link: Dual-loop BDD is the new Red-green TDD</title>
      <link>https://limulus.net/links/2026/03/15/dual-loop-bdd-new-red-green-tdd/</link>
      <description>&lt;p&gt;
  &lt;a href=&quot;https://justin.searls.co/posts/dual-loop-bdd-is-the-new-red-green-tdd/&quot;
  &gt;Justin Searls&lt;/a&gt;:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    My initial prompt simply told the agent to practice “red-green TDD” (a phrase I had
    never heard of until it was discovered that LLMs apparently interpret it as “real TDD”).
    This approach turned out to be woefully insufficient. Why? Because agents follow the
    path of least resistance and will invariably write a shitload of unit tests chasing the
    local maximum of code coverage without any regard for the global maximum of making sure
    shit actually works.
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  While I haven’t had &lt;em&gt;bad&lt;/em&gt; results from the &lt;code&gt;red-green TDD&lt;/code&gt; prompt trick,
  Claude Code has not been doing what I expected with this instruction. What I see is that
  it:
&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Writes most, if not all, of the tests first.&lt;/li&gt;
  &lt;li&gt;Runs the tests to see them fail.&lt;/li&gt;
  &lt;li&gt;Writes the implementation.&lt;/li&gt;
  &lt;li&gt;
    Runs tests again, and if they don’t pass, fixes the implementation and/or the tests in a
    single step. (Opus 4.6 will often say something along the lines of “I’ll fix the tests
    and the code simultaneously.”)
  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
  This is not TDD! At least not as I know it and use it. What I expect the agent to do when
  told to TDD is something more like this:
&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Write a single test.&lt;/li&gt;
  &lt;li&gt;Run the tests to make sure it fails in a &lt;em&gt;nontrivial and expected way&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;
    Write a minimal amount of code to make the test pass (and run the test again to make
    sure it passes).
  &lt;/li&gt;
  &lt;li&gt;Refactor the code, and run the tests to make sure they still pass.&lt;/li&gt;
  &lt;li&gt;Repeat until the planned change is complete.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
  This is why it has been puzzling to me to see folks say that the &lt;code&gt;red-green
    TDD&lt;/code&gt; prompt is actually getting agents to do TDD. You’re not doing TDD if you are
  writing all the tests at once and seeing them all fail for trivial reasons like
  nonexistent symbols/methods/properties. You actually need to see the expectations of the
  tests fail for you to be sure that they are justifying their existence.
&lt;/p&gt;
&lt;p&gt;
  I suspect part of the confusion here is that TDD is a buzzword that has lost its original
  meaning, so when people say their agents are doing TDD they don’t know that they’re not.
&lt;/p&gt;
&lt;p&gt;
  Apparently though, Justin has gotten Opus 4.6 to do something a lot closer to what I have
  been expecting:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    So yesterday I updated the prompt with the more sophisticated dual-loop approach
    developed by folks like &lt;a href=&quot;https://dannorth.net/&quot;&gt;Dan North&lt;/a&gt; and other
    adherents of &lt;a href=&quot;https://en.wikipedia.org/wiki/Behavior-driven_development&quot;
    &gt;behavior-driven development&lt;/a&gt; in the late aughts. It is best illustrated by two
    concentric circles: you begin each feature with a failing integration test, then dive
    into an inner loop for numerous red-green-refactor iterations of unit tests, then pop
    back out again once the outer loop’s integration test passes.
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  I was &lt;a
    href=&quot;https://limulus.net/links/2026/02/03/where-are-the-test-driven-agentic-workflows-anyway/&quot;
  &gt;expecting&lt;/a&gt; that we’d need a mode or custom agent workflow to pull this off. I’m still
  doubtful that strictly adhering to a prompted workflow like this is a strength of agents,
  but I’ll have to give this a try.
&lt;/p&gt;
&lt;p&gt;
  On a related note, I’ve started wondering if agents really need such a fine-grained TDD
  loop. Maybe we just need to ask them to present the tests they plan to write during the
  planning phase, in BDD style.
&lt;/p&gt;
</description>
      <pubDate>Sun, 15 Mar 2026 17:47:46 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/links/2026/03/15/dual-loop-bdd-new-red-green-tdd/</guid>
    </item>
    <item>
      <title>Surviving the AI Revolution in Software Development</title>
      <link>https://limulus.net/links/2026/03/01/surviving-ai-revolution-software-development/</link>
      <description>&lt;p&gt;
  A &lt;a
    href=&quot;https://limulus.net/links/2026/03/01/surviving-ai-revolution-software-development/link&quot;
  &gt;wonderful essay&lt;/a&gt; by Ed Lyons:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Up front, here are the lessons:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Stop listening to people who are afraid&lt;/li&gt;
    &lt;li&gt;Seek first-hand testimony, not opinions&lt;/li&gt;
    &lt;li&gt;Go with someone much more enthusiastic than you&lt;/li&gt;
    &lt;li&gt;Do not look down&lt;/li&gt;
    &lt;li&gt;You must get different equipment&lt;/li&gt;
    &lt;li&gt;Put the summit out of your mind&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Yet I hope you stay for the hike up.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  Via &lt;a href=&quot;https://laughingmeme.org/2026/03/01/software-in-a-time-of-fear.html&quot;
  &gt;laughingmeme.org&lt;/a&gt;.
&lt;/p&gt;
</description>
      <pubDate>Mon, 02 Mar 2026 04:40:08 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/links/2026/03/01/surviving-ai-revolution-software-development/</guid>
    </item>
    <item>
      <title>Good Taste</title>
      <link>https://limulus.net/links/2026/02/07/good-taste/</link>
      <description>&lt;p&gt;
  embedding-shapes writes about how having &lt;a href=&quot;https://emsh.cat/good-taste/&quot;
  &gt;good taste&lt;/a&gt; is the human differentiator:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    I get why many of us fear AI and carry this sense of doom with us, but at the same time,
    as a creative I’m not super worried about it, and not because I think AI cannot generate
    things, or even generate good things, because I think it can. But because the hard thing
    has never been to just produce something. What’s always been hard, is producing
    something good, something made by someone with Good Taste. A real human being that stand
    in front of hundreds of choices and knows (or feels) what gets to stay, what gets cut,
    what to push and what should be refused, and finally sharing the choices they made with
    you, through the medium.
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  They’re right about this, but I have to wonder if enough employers care about good taste.
&lt;/p&gt;
</description>
      <pubDate>Sat, 07 Feb 2026 19:55:26 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/links/2026/02/07/good-taste/</guid>
    </item>
    <item>
      <title>Tahoe’s Windows</title>
      <link>https://limulus.net/links/2026/02/07/tahoes-windows/</link>
      <description>&lt;p&gt;
  &lt;a href=&quot;https://daringfireball.net/2026/01/resizing_windows_macos_26&quot;&gt;John Gruber&lt;/a&gt;:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    The windows on MacOS 26 Tahoe don’t really have comically large, childish corner
    radiuses. They just look like they do because some jackasses at Apple — all of whom, I
    pray, are now at Meta — thought they looked better that way. It’s a straight-up
    inversion of Steve Jobs’s maxim that design is about how things work, not how they look.
    I can think of no better example to prove that the new UI in MacOS 26 Tahoe was designed
    by people who do not understand or care about the most basic fundamental principles of
    good design.
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  In general I think the larger corner radiuses of windows in macOS Tahoe look fine. But if
  Apple really wanted to make this new look functional they really needed to rethink how
  windows work at a more fundamental level. The window’s corners should not be clipping the
  scroll bar. The resize target needs to be increased. Toolbar items should feel like they
  naturally fit in a window’s corner.
&lt;/p&gt;
&lt;p&gt;
  What Apple shipped is how I expect an overly-ambitious Linux theme to work within the
  constraints of X11 — not macOS.
&lt;/p&gt;
</description>
      <pubDate>Sat, 07 Feb 2026 17:45:09 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/links/2026/02/07/tahoes-windows/</guid>
    </item>
    <item>
      <title>Where Are the Test Driven Agentic Workflows, Anyway?</title>
      <link>https://limulus.net/links/2026/02/03/where-are-the-test-driven-agentic-workflows-anyway/</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://blog.fsck.com/2026/02/03/managing-agents/&quot;&gt;Jesse Vincent&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    By my very uncharitable math, agentic software engineering methodologies have gotten to
    somewhere around the 1970s. Like everything in AI, this is, of course, a speed run.
    We’re very quickly relearning why software engineering management and software
    engineering project management matter.
  &lt;/p&gt;
  &lt;p&gt;
    So by next Tuesday, maybe folks will start thinking about what they can steal from XP.
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  I’ve been thinking that we’re probably not that far away from seeing a decent boost in
  quality from coding agents that implement a Test Driven Development workflow.
&lt;/p&gt;
&lt;p&gt;
  Based on my experience, it’s not really possible to get a coding agent to follow TDD on
  its own by instructing it to. Even with the rules of TDD spelled out in an &lt;code
  &gt;AGENTS.md&lt;/code&gt; or similar, I’ve found that they will write tests simultaneously with
  the production code. They don’t bother ensuring they see a test fail for the expected
  reason. I don’t know if that is because the models themselves have trouble following the
  instructions, or if the agent’s system prompts are interfering.
&lt;/p&gt;
&lt;p&gt;
  I honestly wouldn’t be surprised if next Tuesday Claude Code &lt;em&gt;does&lt;/em&gt; ship a TDD mode
  that explicitly takes the LLM through the TDD process.
&lt;/p&gt;
&lt;p&gt;
  &lt;strong&gt;Update:&lt;/strong&gt; Justin Searls has had some success getting Opus 4.6 to follow a
  &lt;a href=&quot;https://limulus.net/links/2026/03/15/dual-loop-bdd-new-red-green-tdd/&quot;
  &gt;dual-loop BDD workflow&lt;/a&gt;.
&lt;/p&gt;
</description>
      <pubDate>Tue, 03 Feb 2026 23:56:10 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/links/2026/02/03/where-are-the-test-driven-agentic-workflows-anyway/</guid>
    </item>
    <item>
      <title>I’m Not Sweating My AI Energy Usage</title>
      <link>https://limulus.net/links/2026/01/30/im-not-sweating-my-ai-energy-usage/</link>
      <description>&lt;p&gt;
  Simon P. Couch used energy-per-token estimates &lt;a
    href=&quot;https://www.simonpcouch.com/blog/2026-01-20-cc-impact/&quot;
  &gt;to calculate his AI coding energy use&lt;/a&gt;:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    On a median day, I estimate I consume 1,300 Wh through Claude Code — 4,400 “typical
    queries” worth. (For those wondering, this is $15-20 of token spend on a typical day.)
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  This number matches my mental model for my own AI energy use. When Claude Code is churning
  away, I picture an ultra-high-end GPU at full utilization dedicated to my request for a
  minute or two.
&lt;/p&gt;
&lt;p&gt;
  Recently I built a gaming PC and hooked it up to my UPS with a wattage output display.
  When playing graphics-intensive games, I see that I’m consuming about 500 W more than I
  would be if I was just using my Mac Studio.
&lt;/p&gt;
&lt;p&gt;
  My median day of AI coding undoubtedly uses less than $15–$20 of token spend. But even if
  it did use that much, it would still be less energy use than 3 hours of flight
  simming.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a
      href=&quot;https://limulus.net/links/2026/01/30/im-not-sweating-my-ai-energy-usage/#fn1&quot;
      id=&quot;fnref1&quot;
    &gt;[1]&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;
&lt;p&gt;
  Granted, both of these newish behaviors (AI coding and PC gaming) are net increases in my
  energy use. But I’m finding it hard to feel bad about either of them, especially when no
  one is telling heavy gamers they should feel ashamed of their energy usage.
&lt;/p&gt;
&lt;p&gt;
  Via &lt;a href=&quot;https://simonwillison.net/2026/Jan/20/electricity-use-of-ai-coding-agents/&quot;
  &gt;simonwillison.net&lt;/a&gt;.
&lt;/p&gt;&lt;section class=&quot;footnotes section-separator&quot;&gt;
  &lt;ol class=&quot;footnotes-list&quot;&gt;
    &lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;
      &lt;p&gt;
        Flight simming for 3 hours (&lt;math display=&quot;inline&quot;&gt;&lt;mn&gt;500&lt;/mn&gt;&lt;mo
            rspace=&quot;thickmathspace&quot;
          &gt;⁢&lt;/mo&gt;&lt;mi mathvariant=&quot;normal&quot;&gt;W&lt;/mi&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;mo
            rspace=&quot;thickmathspace&quot;
          &gt;⁢&lt;/mo&gt;&lt;mi mathvariant=&quot;normal&quot;&gt;hours&lt;/mi&gt;
          &lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;1,500&lt;/mn&gt;&lt;mo rspace=&quot;thickmathspace&quot;&gt;⁢&lt;/mo&gt;&lt;mi mathvariant=&quot;normal&quot;
          &gt;Wh&lt;/mi&gt;&lt;/math&gt;) is more than a typical day of AI coding at the 1,300 Wh estimate.
        &lt;a
          href=&quot;https://limulus.net/links/2026/01/30/im-not-sweating-my-ai-energy-usage/#fnref1&quot;
          class=&quot;footnote-backref&quot;
        &gt;↩︎&lt;/a&gt;
      &lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Sat, 31 Jan 2026 06:58:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/links/2026/01/30/im-not-sweating-my-ai-energy-usage/</guid>
    </item>
    <item>
      <title>Node.js Now Has TypeScript Type Stripping On By Default</title>
      <link>https://limulus.net/tils/nodejs/type-stripping/</link>
      <description>&lt;p&gt;
  Despite keeping up better with my RSS feed reader lately, I still missed that &lt;a
    href=&quot;https://nodejs.org/en/blog/release/v22.18.0&quot;
  &gt;Node.js v22.18.0&lt;/a&gt; enables TypeScript &lt;a
    href=&quot;https://nodejs.org/en/learn/typescript/run-natively&quot;
  &gt;type stripping&lt;/a&gt; by default! Apparently it has also been enabled in v24 since its
  initial release.
&lt;/p&gt;
&lt;p&gt;
  This is great news, because it means that with some important caveats, it is now possible
  to run TypeScript natively in Node without needing to specify the &lt;code
  &gt;--experimental-strip-types&lt;/code&gt;
  flag. The caveats boil down to the fact that not every TypeScript language feature can be
  cleanly stripped to create valid JavaScript — a small handful of features require the
  TypeScript compiler to generate JavaScript code in order to fully function.
&lt;/p&gt;
&lt;p&gt;
  Luckily, in TypeScript v5.8 they introduced the &lt;a
    href=&quot;https://devblogs.microsoft.com/typescript/announcing-typescript-5-8/#the---erasablesyntaxonly-option&quot;
  &gt;--erasableSyntaxOnly option&lt;/a&gt;. This raises errors when you use one of these features
  that would cause Node.js to throw a JS parse error even after type stripping.
&lt;/p&gt;
&lt;p&gt;
  The full list of these non-erasable language features is thankfully small, and, in my
  usage of TypeScript, quite manageable:
&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;namespace&lt;/code&gt;s and &lt;code&gt;module&lt;/code&gt;s with runtime code&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;import =&lt;/code&gt; aliases&lt;/li&gt;
  &lt;li&gt;
    Parameter properties in class constructors (the shorthand where &lt;code&gt;class {
      constructor(readonly foo: Bar) {} }&lt;/code&gt; auto-creates a &lt;code&gt;foo&lt;/code&gt; prop)
  &lt;/li&gt;
  &lt;li&gt;&lt;code&gt;enum&lt;/code&gt; declarations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  I don’t think I’ve ever used executable code in a &lt;code&gt;namespace&lt;/code&gt; or a &lt;code
  &gt;module&lt;/code&gt; — the only time I use those is if I need to extend global or package types.
  I don’t think I’ve ever used
  &lt;code&gt;import =&lt;/code&gt; aliases. And I can easily live without parameter properties.
&lt;/p&gt;
&lt;p&gt;
  Lack of enums is probably the toughest pill to swallow. But the TypeScript docs for enums
  have a &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums&quot;
  &gt;good suggestion&lt;/a&gt; — instead of creating an &lt;code&gt;enum&lt;/code&gt; create an object with
  &lt;code&gt;as const&lt;/code&gt;:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Direction&lt;/span&gt; = {
  &lt;span class=&quot;hljs-title class_&quot;&gt;Up&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;,
  &lt;span class=&quot;hljs-title class_&quot;&gt;Right&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;,
  &lt;span class=&quot;hljs-title class_&quot;&gt;Down&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;,
  &lt;span class=&quot;hljs-title class_&quot;&gt;Left&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;4&lt;/span&gt;,
} &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;DirectionEnum&lt;/span&gt; = (&lt;span class=&quot;hljs-keyword&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Direction&lt;/span&gt;)[keyof &lt;span class=&quot;hljs-keyword&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Direction&lt;/span&gt;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  Hopefully &lt;a href=&quot;https://github.com/microsoft/TypeScript/issues/60790&quot;&gt;this proposal&lt;/a&gt;
  for an &lt;code&gt;as enum&lt;/code&gt; assertion gets some traction, since it would be nice to not
  need an extra &lt;code&gt;[Name]Enum&lt;/code&gt; type.
&lt;/p&gt;
&lt;p&gt;
  There are two other TS options that are helpful for running type-stripped TS in Node:
  &lt;code&gt;rewriteRelativeImportExtensions&lt;/code&gt; and &lt;code&gt;verbatimModuleSyntax&lt;/code&gt;. The
  former helps ensure you can use &lt;code&gt;.ts&lt;/code&gt; extensions in your &lt;code&gt;import&lt;/code&gt;
  statements, and the latter avoids surprises from top-level side-effects when &lt;code
  &gt;import&lt;/code&gt;ing a TS module.
&lt;/p&gt;
</description>
      <pubDate>Sat, 01 Nov 2025 20:45:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/tils/nodejs/type-stripping/</guid>
    </item>
    <item>
      <title>Private Branches in Public Repos</title>
      <link>https://limulus.net/tils/git/private-branches-in-public-repos/</link>
      <description>&lt;p&gt;
  Something that’s semi-frequently bugged me about having this site be statically generated
  from flat files in a &lt;a href=&quot;https://github.com/limulus/limulus-dot-net/&quot;
  &gt;public GitHub repository&lt;/a&gt; is how to deal with drafts of posts. I want to be able to
  save my progress “in the cloud” so that I can pick it up on other machines without making
  anything half-baked publicly accessible.
&lt;/p&gt;
&lt;p&gt;
  The way I have been dealing with this is to use a persistent GitHub Codespace where
  changes can just sit untracked. But this means waiting for the Codespace to initialize
  when I start working on something, dealing with it pausing after inactivity, or even
  paying a bit for it if I go over GitHub’s free allotment. Without increasing the number of
  vCPUs on the Codespace, it’s also significantly slower than any machine I would use.
&lt;/p&gt;
&lt;p&gt;
  So a few times I’ve gone looking for a better solution to this problem and I’ve come up
  empty. But last night I decided to try again, and I gave &lt;a
    href=&quot;https://claude.ai/share/6c771cdd-77b1-4d6a-ba40-bba69f57d4b7&quot;
  &gt;this prompt&lt;/a&gt; to Claude:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    I wish I could have encrypted WIP branches in public GitHub repos. Does such a thing
    exist?
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  The response told me a bit about encryption options, but the first two alternatives it
  gave hints at something better:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;
      Private branches - Just use a private repo for WIP, then move to public when ready
    &lt;/li&gt;
    &lt;li&gt;Fork workflow - Keep your fork private while the upstream is public&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  This made something click for me. If I created a private fork that only exists to store
  branches of work-in-progress, then I could add it as a remote to clones of the public
  repo. This would effectively let me have “private branches”! Finally!
&lt;/p&gt;
&lt;p&gt;
  I’ve implemented this by creating an empty private GitHub repo, added it as a remote named
  &lt;code&gt;private&lt;/code&gt;, pushed up everything from the original, and renamed the &lt;code
  &gt;origin&lt;/code&gt; remote to
  &lt;code&gt;public&lt;/code&gt;. This rename makes sure I will need to explicitly specify a remote
  when pushing new branches, preventing accidental pushes to the public repo.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs&quot;&gt;git remote add private https://github.com/limulus/limulus-dot-net-private.git
git push private --all
git remote rename origin public
git checkout -b my-wip
git push -u private my-wip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  When it comes time to merge my work into the public &lt;code&gt;main&lt;/code&gt; branch, I can do
  this easily with plain old &lt;code&gt;git merge&lt;/code&gt;. If there’s intermediate commits in the
  branch that I don’t want to be public I can always rebase or squash.
&lt;/p&gt;
&lt;p&gt;
  GitHub unfortunately does not track this separate private repo as a fork. That (plus the
  public/private dichotomy) means I can’t directly make PRs from the private repo. If I were
  using a PR based workflow, I expect that would have to reset the upstream of the branch to
  &lt;code&gt;public&lt;/code&gt; and then make a PR. But that’s not something I’m planning on doing
  with this project.
&lt;/p&gt;
&lt;p&gt;
  This is one of those things where I feel a little silly for not realizing this was
  possible. It’s obvious in retrospect that the ability to have multiple remotes in a
  repository would allow for this. But it’s a git feature I’ve used at most once before.
&lt;/p&gt;
</description>
      <pubDate>Sat, 11 Oct 2025 21:47:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/tils/git/private-branches-in-public-repos/</guid>
    </item>
    <item>
      <title>The Social Contract</title>
      <link>https://limulus.net/feed/the-social-contract/</link>
      <description>&lt;p&gt;
  Maybe you’ve heard of this thing called the &lt;a
    href=&quot;https://en.wikipedia.org/wiki/Social_contract&quot;
  &gt;social contract&lt;/a&gt;. It’s a concept in philosophy popularized during the &lt;a
    href=&quot;https://en.wikipedia.org/wiki/Age_of_Enlightenment&quot;
  &gt;Enlightenment&lt;/a&gt; that helps explain why anyone should submit themselves to the authority
  of a government. The idea is simple: we agree that the government gets to do things like
  imprison some of us, so long as the government meets a standard of evidence and the law is
  fair.
&lt;/p&gt;
&lt;p&gt;
  In the United States we call a good chunk of things covered under our social contract &lt;a
    href=&quot;https://en.wikipedia.org/wiki/Due_process&quot;
  &gt;due process&lt;/a&gt;. If the government wishes to do something adverse to someone, they are
  &lt;em&gt;due&lt;/em&gt; a
  &lt;em&gt;process&lt;/em&gt; that ensures their rights are respected and that the actions of the
  government are carried out lawfully. Without due process, there is nothing to constrain
  the government from harming someone for any arbitrary reason.
&lt;/p&gt;
&lt;p&gt;
  There is a sense among many people in this country that they are not beneficiaries of this
  arrangement — that only criminals benefit from due process. This could not be further from
  the truth. Due process is what prevents a local politician from seizing your property
  simply because they want to. It’s what keeps you out of jail for accidentally insulting
  the mayor’s buddy. It’s what saves you from undue prosecution if you harm someone while
  acting in self-defense.
&lt;/p&gt;
&lt;p&gt;
  In this country we have been gifted a high standard of due process throughout all levels
  of government. From the town police, to the county sheriff, to the state police, to the
  federal Department of Justice. Many seem to believe we have too-high a standard; that it’s
  high time that we abandon this principle — at least for some people.
&lt;/p&gt;
&lt;p&gt;
  Due process does not mean everyone is treated exactly the same without regard for
  efficiency. For example, our immigration courts are operated by the executive branch
  rather than the judiciary. Noncitizens in an immigration court are not entitled to a jury
  trial. Nevertheless, this process is established by laws passed by Congress and its
  decisions are ultimately reviewable by the judicial branch. It does not mean the executive
  gets to do whatever it wants without regards to what the law says or a court orders.
&lt;/p&gt;
&lt;h2 id=&quot;what-you-probably-already-figured-this-post-was-about&quot; tabindex=&quot;-1&quot;&gt;
  What you probably already figured this post was about
&lt;/h2&gt;
&lt;p&gt;
  This post is mostly about Abrego Garcia. While I do find the evidence he is an MS-13 gang
  member thin and speculative, that is ultimately immaterial. He could be incapable of
  hurting a fly or he could be a bona fide terrorist. The court order preventing his
  deportation to El Salvador should not have been defied. The court order the Supreme Court
  upheld with no dissents should not have been defied. These acts of defiance violate our
  long established social contract.
&lt;/p&gt;
&lt;p&gt;
  I want to convince everyone of this, but sadly I doubt it is possible to convince those
  who believe the government is justified in their defiance. I find it unlikely they can be
  made to understand that someday this will be used against them. The prose of &lt;a
    href=&quot;https://limulus.net/feed/the-social-contract/garcia-v-noem-order-on-appeal.pdf&quot;
  &gt;Judge Wilkinson’s order&lt;/a&gt; in this case — even though it is as compelling an appeal to
  American values as any decision of a court can be — seems unlikely to persuade many
  Americans who have never held these values in high regard.
&lt;/p&gt;
&lt;p&gt;
  When discussing rights, we often talk about how things like due process protects &lt;em
  &gt;your&lt;/em&gt;
  rights when &lt;em&gt;you&lt;/em&gt; are accused of a crime. Heck, it is how I wrote the first draft
  of this post. Unfortunately, I don’t believe such people are capable of thinking of
  themselves as the &lt;em&gt;you&lt;/em&gt; in this scenario. It is unimaginable to them that the &lt;a
    href=&quot;https://www.reddit.com/r/LeopardsAteMyFace/comments/lt8zlq/a_guide_to_this_subs_explanatory_comment_rule/&quot;
  &gt;leopards will eat &lt;em&gt;their&lt;/em&gt;
    face&lt;/a&gt;. I’m not sure why so many are like this but I suspect it’s fundamental to how
  their brains work and the only thing that can sometimes change that wiring is when the
  leopard finally eats their face.
&lt;/p&gt;
&lt;p&gt;
  When talking to folks about this, it may be &lt;a
    href=&quot;https://mstdn.social/@marick/114367460709809608&quot;
  &gt;more effective&lt;/a&gt; to make the argument about how they been benefiting from due process
  all along. It’s what stands between us and things like corrupt local party officials. We
  must continue to exercise this bedrock principle of our social contract lest we lose it
  completely and begin a descent to into a culture tolerant of corruption.
&lt;/p&gt;
&lt;h2 id=&quot;the-consequences&quot; tabindex=&quot;-1&quot;&gt;The consequences&lt;/h2&gt;
&lt;p&gt;
  When it became clear that the Trump administration had decided to defy court orders, I
  &lt;a href=&quot;https://mastodon.limulus.net/@eric/114338885155234291&quot;&gt;posted this&lt;/a&gt;:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    An executive that defies court orders is undeserving of the safety that the social
    contract would otherwise afford them.
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  “Safety” here is a spectrum, but it includes feeling secure in one’s office. Repeated
  defiance of the courts must be met with serious threats of removal through lawful means,
  such as impeachment. While that is unlikely to succeed immediately given the current
  make-up of Congress and President Trump’s continued popularity among the Republican base
  — it is not impossible if these factors shift. To that end, there is a path to bring an
  early end to Trump’s un-American pursuit of authoritarianism: make the 2026 midterm
  elections a referendum on impeaching Trump and Vice President Vance, likewise making the
  newly-minted Speaker Hakeem Jeffries president for the remainder of the term.
&lt;/p&gt;
&lt;p&gt;
  A contract with someone acting in bad faith only works if there are consequences for
  violating it.
&lt;/p&gt;
</description>
      <pubDate>Sat, 26 Apr 2025 18:12:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/feed/the-social-contract/</guid>
    </item>
    <item>
      <title>Workout Exporter</title>
      <link>https://limulus.net/feed/workout-exporter/</link>
      <description>&lt;p&gt;
  Last year, I got a GoPro HERO12 Black, which I have been using to record my bike rides.
  Naturally, I also track these as workouts using my Apple Watch. I’ve been thinking it
  might be neat to synchronize a map, heart rate, and other data from HealthKit with the
  video. It probably won’t make for a particularly compelling watch, but it’s something I’d
  like to have.
&lt;/p&gt;
&lt;p&gt;The rough plan to make this work is to do the following:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    Make a little iOS app for exporting workouts into a machine readable format, preferably
    a standardized format so it can be used for other purposes.
  &lt;/li&gt;
  &lt;li&gt;
    Write a program that reads both the workout data and an XML export for a Final Cut Pro
    project and generates a WebVTT file with the workout data in JSON. This &lt;a
      href=&quot;https://en.wikipedia.org/wiki/WebVTT&quot;
    &gt;WebVTT&lt;/a&gt; file would then be included as a track alongside the video.
  &lt;/li&gt;
  &lt;li&gt;Create web components that overlay or go alongside the video to display the data.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
  One of these components is a map, something I’ve already worked on for &lt;a
    href=&quot;https://limulus.net/photos/&quot;
  &gt;my photos&lt;/a&gt;. For example, scroll down on &lt;a
    href=&quot;https://limulus.net/photos/one-long-cut/&quot;
  &gt;One Long Cut&lt;/a&gt; to see a map of where it was taken.
&lt;/p&gt;
&lt;p&gt;
  Anyway, I’ve been working on the first step of this plan and now have something to show
  for it! I’m calling it simply &lt;a href=&quot;https://github.com/limulus/workout-exporter&quot;
  &gt;Workout Exporter&lt;/a&gt;. It’s a SwiftUI app that lets you choose a recent workout, writes a
  &lt;a href=&quot;https://en.wikipedia.org/wiki/Training_Center_XML&quot;&gt;TCX&lt;/a&gt; file with the workout
  data, and lets you export it using a share sheet where you can AirDrop it or save it to
  Files.
&lt;/p&gt;
&lt;p&gt;
  There are still some rough edges, particularly with export naming and the fact that it
  shares is a &lt;code&gt;.txt&lt;/code&gt; instead of a &lt;code&gt;.xml&lt;/code&gt; or &lt;code&gt;.tcx&lt;/code&gt; file.
  I’ll refine this over time, as I’ll undoubtedly need to tweak the export at some point.
&lt;/p&gt;
&lt;p&gt;
  This was also my first time doing assisted coding with Claude as an LLM and also with
  &lt;a href=&quot;https://github.com/anthropics/claude-code&quot;&gt;Claude Code&lt;/a&gt; — or any coding agent
  for that matter. Since my experience with SwiftUI is limited, this is a case where LLM
  assistance was helpful in learning idioms that Apple’s documentation alone rarely conveys.
  I’ll be trying it out more with the next step of this project to get a better feel what
  working with an agent is like when it comes to a language I am considerably more familiar
  with.
&lt;/p&gt;
</description>
      <pubDate>Sun, 16 Mar 2025 06:10:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/feed/workout-exporter/</guid>
    </item>
    <item>
      <title>I Wrote To My Representative</title>
      <link>https://limulus.net/feed/write-to-your-rep/</link>
      <description>&lt;p&gt;
  One thing I suspect partisans like myself underappreciate is the extent to which members
  of Congress are driven by the concerns their constituents choose to share. Back in my days
  on Twitter, I followed someone who used to work as a congressional staffer and this was
  their common refrain.
&lt;/p&gt;
&lt;p&gt;
  I used to write my congressperson occasionally, but it has been well over a decade since I
  have. Part of the reason for this has been a creeping cynicism that this doesn’t do much
  good. But I think I’ve been getting this wrong. So yesterday I wrote the following to my
  Republican congressman, Juan Ciscomani:
&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Dear Representative Ciscomani,&lt;/p&gt;
  &lt;p&gt;
    I am writing to express my deep concern over the recent events unfolding in the
    Executive Branch, particularly those in which involve the Department of Government
    Efficiency (DOGE) and its leader, Elon Musk.
  &lt;/p&gt;
  &lt;p&gt;
    As a professional software engineer who was on Twitter during Musk’s takeover, I watched
    in disbelief as Musk publicly ridiculed and derided the engineering work of his new
    employees. While many of the layoffs were in no doubt necessary for Twitter to remain
    profitable after the buyout, Musk could have chosen to treat those who had to be let go
    with the respect they deserved, given their contributions to making and keeping Twitter
    a company worth $44 billion. Instead Musk chose a chaotic, mean spirited, and reckless
    path. By all accounts, Twitter (now X) is greatly devalued with advertisers increasingly
    avoiding engaging in business with it.
  &lt;/p&gt;
  &lt;p&gt;
    This very public display of disrespect was a turning point of opinion for many
    experienced software engineers of Musk. It seems he now surrounds himself with young and
    inexperienced acolytes — perhaps these are the only kind of people who do not challenge
    him. Sadly, it seems he has brought these people into DOGE and installed them in places
    where their inexperience and past associations with online gangs such as “The Com” could
    put government computer systems in danger of compromise.
  &lt;/p&gt;
  &lt;p&gt;
    Musk’s vendetta against the United States Agency for International Development (USAID)
    is another area where his capriciousness threatens the security of the United States. By
    disrupting and potentially completely stopping aid programs the standing of the U.S.
    will worsen and China will step in and fill the void.
  &lt;/p&gt;
  &lt;p&gt;
    I do not believe most of those who voted for Trump in Arizona’s 6th Congressional
    District had this sort of mayhem and disregard for security in mind when they cast their
    votes. My hope is that you will sponsor congressional action to rein in Musk and DOGE.
  &lt;/p&gt;
  &lt;p&gt;Sincerely,&lt;br&gt;Eric McCarthy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ll be resending this to my Democratic senators as well.&lt;/p&gt;
&lt;p&gt;
  My approach to writing these letters is to attempt to persuade without being overly
  emotive and establish my credibility. I like to make it clear what my area of expertise is
  and how it is relevant to my concern. Keeping it to the facts and tying those facts to my
  concern make it more difficult for my concern to be dismissed.
&lt;/p&gt;
&lt;p&gt;
  I also don’t like to bring up my party affiliation or whether or not I voted for them. I’m
  their constituent regardless. It’s tempting to say that my vote for them is contingent on
  them taking action to address my concern, but I think leaving the door open to the
  possibility that I am a swing voter without ever explicitly mentioning it is more than
  enough.
&lt;/p&gt;
&lt;p&gt;
  One thing I did not do a good job of in this letter is being specific as to what action
  I’d like to see. When it comes to votes on legislation this is pretty straightforward. But
  in this instance I really don’t know what a good course of action for my Republican
  congressman would be. It may not be something specifically legislative in nature. Even
  just talking to a journalist and saying that he has reservations about Musk and DOGE would
  be welcome as it would open the doors for more Republicans to do the same.
&lt;/p&gt;
&lt;p&gt;
  If you want to write to your members of Congress, enter your address on the &lt;a
    href=&quot;https://www.congress.gov/members/find-your-member&quot;
  &gt;Find Your Members&lt;/a&gt; form of congress.gov. The “Contact” link should go directly to a
  web form to submit your note. My understanding is that this is generally the preferred way
  to express written concerns — snail mail requires a longer processing time. I’m not sure
  if by phone is any better or worse, but I’m a much better writer than I am a phone-talker
  so I just go with that.
&lt;/p&gt;
</description>
      <pubDate>Sun, 09 Feb 2025 21:35:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/feed/write-to-your-rep/</guid>
    </item>
    <item>
      <title>New York 2024</title>
      <link>https://limulus.net/feed/new-york-2024/</link>
      <description>&lt;p&gt;
  In October Rebecca and I visited my parents on Long Island and took a week to drive around
  to various parks in upstate New York. It was a great trip! We took over 3 hours of video
  which I edited down to 25 minutes to create the above travelogue.
&lt;/p&gt;
&lt;p&gt;
  At some point in the future I’ll get around to making linkable chapters in videos, but
  here’s what you can expect if you watch the entire thing:
&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    We milled around &lt;a href=&quot;https://www.mattituckparks.com/bailie-beach-mattituck&quot;
    &gt;Bailie Beach&lt;/a&gt;. There’s an interesting structure seemingly made from driftwood which
    was presumably constructed by the beachfront property owners.
  &lt;/li&gt;
  &lt;li&gt;
    Some still shots from the Aurora Borealis which was visible to the naked eye, but shows
    up impressively on cell phone cameras. The last two photos are also from Bailie Beach.
  &lt;/li&gt;
  &lt;li&gt;
    A bit of footage from our voyage on the P.T. Barnum of the &lt;a
      href=&quot;https://www.88844ferry.com/&quot;
    &gt;Port Jefferson Ferry&lt;/a&gt; from Long Island to Connecticut on our way to stay in Ithaca,
    New York.
  &lt;/li&gt;
  &lt;li&gt;
    Our walk through &lt;a href=&quot;https://parks.ny.gov/parks/watkinsglen/&quot;&gt;Watkins Glen&lt;/a&gt;. It
    was very crowded that day!
  &lt;/li&gt;
  &lt;li&gt;
    Our walk to the beautiful &lt;a href=&quot;https://parks.ny.gov/parks/taughannockfalls/&quot;
    &gt;Taughannock Falls&lt;/a&gt;.
  &lt;/li&gt;
  &lt;li&gt;
    We spent a day at the &lt;a href=&quot;https://www.cmog.org/&quot;&gt;Corning Museum of Glass&lt;/a&gt; and I
    recorded &lt;a href=&quot;https://manologlass.com/&quot;&gt;Manolo Aguilera&lt;/a&gt; creating a glass nopal
    (prickly pear pad) for a greater glass sculpture. &lt;a
      href=&quot;https://img1.wsimg.com/isteam/ip/2a6ef6f6-2409-444e-9f07-55c848654433/Sculpture-2-v3.jpg&quot;
    &gt;This one&lt;/a&gt; — I think.
  &lt;/li&gt;
  &lt;li&gt;
    We hiked over 6 miles at &lt;a href=&quot;https://parks.ny.gov/parks/letchworth/&quot;
    &gt;Letchworth State Park&lt;/a&gt; and saw the three major waterfalls.
  &lt;/li&gt;
  &lt;li&gt;
    Then we visited &lt;a href=&quot;https://parks.ny.gov/parks/niagarafallsusa/&quot;&gt;Niagara Falls&lt;/a&gt;
    and went on the Nikola Tesla, a &lt;a href=&quot;https://www.maidofthemist.com/&quot;
    &gt;Maid of the Mist&lt;/a&gt; boat.
  &lt;/li&gt;
  &lt;li&gt;
    Finally we went &lt;a
      href=&quot;https://www.niagaraparks.com/visit/attractions/journey-behind-the-falls/&quot;
    &gt;Behind the Falls&lt;/a&gt; after crossing the &lt;a
      href=&quot;https://en.wikipedia.org/wiki/Rainbow_Bridge_(Niagara_Falls)&quot;
    &gt;Rainbow Bridge&lt;/a&gt; into Ontario.
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  Most of this was shot on my iPhone 16 Pro in 4K or my GoPro HERO12 Black in 4K, with some
  HD footage from my phone or Rebecca’s iPhone 14 Pro. I edited it in Final Cut Pro and got
  it ready for hosting here with the help of my &lt;a
    href=&quot;https://github.com/limulus/media-tools/&quot;
  &gt;media tools&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
  Not included in this video is our exploration of Ithaca and Rochester. We spent a good
  amount of time wandering around my alma mater, &lt;a href=&quot;https://www.rochester.edu/&quot;
  &gt;University of Rochester&lt;/a&gt;, and the &lt;a href=&quot;https://www.eastman.org/&quot;
  &gt;Eastman Museum&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
  I still need to go through the photos and post more here, though you can see some from
  Letchworth that I used for &lt;a href=&quot;https://limulus.net/feed/the-rebound/&quot;
  &gt;The Rebound&lt;/a&gt;.
&lt;/p&gt;
</description>
      <pubDate>Sat, 28 Dec 2024 06:05:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/feed/new-york-2024/</guid>
    </item>
    <item>
      <title>The Rebound</title>
      <link>https://limulus.net/feed/the-rebound/</link>
      <description>&lt;p&gt;This used to be a proper website.&lt;/p&gt;
&lt;p&gt;
  It had a blog where I would write on politics. It had a page for a small bit of open
  source software I maintained. At one point it even had a photo gallery.
&lt;/p&gt;
&lt;p&gt;
  Over the years it moved from Movable Type, to Wordpress, to some custom PHP. And then bit
  by bit it became a chore to maintain. “Platforms” like Twitter and Facebook grabbed my
  attention and made posting easy.
&lt;/p&gt;
&lt;p&gt;
  So limulus.net languished. I deleted it piece by piece. Until all that remained was a
  single little homepage with some hand-written HTML. I even &lt;a
    href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410&quot;
  &gt;410ed&lt;/a&gt; my blog.
&lt;/p&gt;
&lt;article-photo id=&quot;middle-falls-letchworth-state-park&quot; placement=&quot;right&quot; class=&quot;wdam8yees&quot;&gt;
  &lt;figure&gt;
    &lt;a href=&quot;https://limulus.net/photos/middle-falls-letchworth-state-park/&quot;&gt;&lt;pho-picture
        placement=&quot;right&quot;
        class=&quot;wdam8yees&quot;
      &gt;
        &lt;blur-hash blurhash=&quot;LLF$eQ%2M{oe}OMIWWt7^hs;t7V?&quot; width=&quot;3000&quot; height=&quot;2250&quot;&gt;
          &lt;picture&gt;
            &lt;source
              srcset=&quot;https://pho.limulus.net/9S_9E3mC/lg.k1DmqvnF.avif 3000w, https://pho.limulus.net/9S_9E3mC/md.kcfUWaQS.avif 1500w&quot;
              type=&quot;image/avif&quot;
              width=&quot;3000&quot;
              height=&quot;2250&quot;
            &gt;
            &lt;source
              srcset=&quot;https://pho.limulus.net/9S_9E3mC/lg.agZzvjVn.jpeg 3000w, https://pho.limulus.net/9S_9E3mC/md.QvAT4NB7.jpeg 1500w&quot;
              type=&quot;image/jpeg&quot;
              width=&quot;3000&quot;
              height=&quot;2250&quot;
            &gt;
            &lt;img
              async=&quot;&quot;
              crossorigin=&quot;anonymous&quot;
              loading=&quot;lazy&quot;
              decoding=&quot;async&quot;
              src=&quot;https://pho.limulus.net/9S_9E3mC/lg.agZzvjVn.jpeg&quot;
              srcset=&quot;https://pho.limulus.net/9S_9E3mC/lg.agZzvjVn.jpeg 3000w, https://pho.limulus.net/9S_9E3mC/md.QvAT4NB7.jpeg 1500w&quot;
              alt=&quot;The Genesee River flows over the Middle Falls in Letchworth State Park. Trees on the bank of the river are turning to fall covers as the low sun illuminates them. In the foreground, several people stand on an overlook photographing the falls. One of them is somewhat precariously standing on a wet rock wall with a tripod.&quot;
              width=&quot;3000&quot;
              height=&quot;2250&quot;
              placement=&quot;right&quot;
              class=&quot;wdam8yees&quot;
            &gt;
          &lt;/picture&gt;
        &lt;/blur-hash&gt;
        &lt;p&gt;&lt;/p&gt;&lt;/pho-picture&gt;&lt;/a&gt;
    &lt;figcaption&gt;
      &lt;span&gt;Middle Falls, Letchworth State Park&lt;/span&gt;
    &lt;/figcaption&gt;
    &lt;p&gt;&lt;/p&gt;
  &lt;/figure&gt;
&lt;/article-photo&gt;
&lt;h2 id=&quot;the-last-glacial-period&quot; tabindex=&quot;-1&quot;&gt;The Last Glacial Period&lt;/h2&gt;
&lt;p&gt;
  The &lt;a href=&quot;https://en.wikipedia.org/wiki/Laurentide_ice_sheet&quot;&gt;Laurentide Ice Sheet&lt;/a&gt;
  once covered much of North America. Over tens of thousands of years it accumulated frozen
  water, spreading further south. It didn’t simply cover land as its glaciers rolled forward
  — it scraped, crushed, and ground the earth under enormous pressure. It and its sibling
  ice sheets of the period sequestered so much of the planet’s water that sea levels &lt;a
    href=&quot;https://en.wikipedia.org/w/index.php?title=Post-glacial_rebound&amp;amp;oldid=1257075757#Global_sea_levels&quot;
  &gt;fell 120 meters&lt;/a&gt;.
&lt;/p&gt;
&lt;h2 id=&quot;elon-musk%E2%80%99s-personal-homepage-on-the-world-wide-web&quot; tabindex=&quot;-1&quot;&gt;
  Elon Musk’s Personal Homepage on the World Wide Web
&lt;/h2&gt;
&lt;p&gt;
  The reality of the online world that we built didn’t really make itself fully apparent to
  me until Elon Musk’s purchase of Twitter. The company was flawed before his ownership but
  his gross public treatment of Twitter’s engineers — now his own employees! — prompted me
  to swear to myself that I would never work for a company of his nor would I ever purchase
  a product from one of his companies given a choice.
&lt;/p&gt;
&lt;p&gt;
  Prior to this — like many others — I had scaled back my usage of Facebook after the 2016
  U.S. Presidential election. Seeing the under-informed and often outright racist opinions
  of distant family members and high school friends put me off using it. At some point
  Facebook started down-ranking political posts in their feed algorithm both to avoid a
  perception of political bias but also because it was clearly turning off users.
  Paradoxically this decreased Facebook’s relevancy to me — what good is it if my posts
  won’t be seen by people?
&lt;/p&gt;
&lt;p&gt;
  I stayed on Twitter for a number of months after the purchase, but when it became apparent
  that my values could not abide staying on Twitter I began my exit to decentralized social
  media. I’ll be honest, I was surprised such a thing existed or even could exist. My
  assumption had always been that social media at a large scale would require
  centralization. But I was wrong. It turned out this entire time folks had been working on
  a &lt;a href=&quot;https://activitypub.rocks/&quot;&gt;web standard&lt;/a&gt;
  to make decentralization work and open-source social media server software like &lt;a
    href=&quot;https://joinmastodon.org/&quot;
  &gt;Mastodon&lt;/a&gt;
  was proving it out.
&lt;/p&gt;
&lt;article-photo
  id=&quot;mist-middle-falls-letchworth-state-park&quot;
  placement=&quot;left&quot;
  class=&quot;wdam8yees&quot;
&gt;
  &lt;figure&gt;
    &lt;a
      href=&quot;https://limulus.net/photos/mist-middle-falls-letchworth-state-park/&quot;
    &gt;&lt;pho-picture placement=&quot;left&quot; class=&quot;wdam8yees&quot;&gt;
        &lt;blur-hash blurhash=&quot;LGDl~9_NE14n-:RPofxusiIUM|tR&quot; width=&quot;3000&quot; height=&quot;2250&quot;&gt;
          &lt;picture&gt;
            &lt;source
              srcset=&quot;https://pho.limulus.net/PlumNgPF/lg.AxTZYQKH.avif 3000w, https://pho.limulus.net/PlumNgPF/md.tvGtgmZu.avif 1500w&quot;
              type=&quot;image/avif&quot;
              width=&quot;3000&quot;
              height=&quot;2250&quot;
            &gt;
            &lt;source
              srcset=&quot;https://pho.limulus.net/PlumNgPF/lg.BrTe_Czt.jpeg 3000w, https://pho.limulus.net/PlumNgPF/md.2Xg2WBYZ.jpeg 1500w&quot;
              type=&quot;image/jpeg&quot;
              width=&quot;3000&quot;
              height=&quot;2250&quot;
            &gt;
            &lt;img
              async=&quot;&quot;
              crossorigin=&quot;anonymous&quot;
              loading=&quot;lazy&quot;
              decoding=&quot;async&quot;
              src=&quot;https://pho.limulus.net/PlumNgPF/lg.BrTe_Czt.jpeg&quot;
              srcset=&quot;https://pho.limulus.net/PlumNgPF/lg.BrTe_Czt.jpeg 3000w, https://pho.limulus.net/PlumNgPF/md.2Xg2WBYZ.jpeg 1500w&quot;
              alt=&quot;Sun-lit mist scatters in the foreground. A blurry person in a hat with a backpack uses their phone to take a photograph of a waterfall.&quot;
              width=&quot;3000&quot;
              height=&quot;2250&quot;
              placement=&quot;left&quot;
              class=&quot;wdam8yees&quot;
            &gt;
          &lt;/picture&gt;
        &lt;/blur-hash&gt;
        &lt;p&gt;&lt;/p&gt;&lt;/pho-picture&gt;&lt;/a&gt;
    &lt;figcaption&gt;
      &lt;span&gt;Mist, Middle Falls, Letchworth State Park&lt;/span&gt;
    &lt;/figcaption&gt;
    &lt;p&gt;&lt;/p&gt;
  &lt;/figure&gt;
&lt;/article-photo&gt;
&lt;p&gt;
  Getting on Mastodon awakened in me a renewed enthusiasm for the web and having a place of
  my own. After a too-brief period on an instance named mastodon.lol, I migrated my account
  to my own Mastodon instance at &lt;a href=&quot;https://mastodon.limulus.net/&quot;
  &gt;mastodon.limulus.net&lt;/a&gt;. (Go follow me at &lt;a href=&quot;https://limulus.net/@eric&quot;
  &gt;eric@limulus.net&lt;/a&gt;!)
&lt;/p&gt;
&lt;h2 id=&quot;the-glaciers-of-new-york&quot; tabindex=&quot;-1&quot;&gt;The Glaciers of New York&lt;/h2&gt;
&lt;p&gt;
  As Earth started to warm about 20,000 years ago the Laurentide Ice Sheet began its
  recession. But it was not a linear process. Some &lt;a
    href=&quot;https://experts.illinois.edu/en/publications/evidence-for-a-late-glacial-advance-near-the-beginning-of-the-you&quot;
  &gt;14,000 years ago&lt;/a&gt; a glacier flowed south from current-day Rochester, New York. It
  covered an area almost as far south as the Pennsylvania state line. The glaciers were not
  yet done transforming the land.
&lt;/p&gt;
&lt;h2 id=&quot;billionaires-only-want-one-thing-and-it%E2%80%99s-disgusting&quot; tabindex=&quot;-1&quot;&gt;
  Billionaires Only Want One Thing and It’s Disgusting
&lt;/h2&gt;
&lt;p&gt;
  As I write this it is a little over a week after Donald Trump’s second presidential
  election win. It seems I am not alone in looking at how things have turned out and being
  dismayed at the &lt;a
    href=&quot;https://newrepublic.com/post/188197/trump-media-information-landscape-fox&quot;
  &gt;outsized influence&lt;/a&gt; billionaires have on us. Trump himself is a billionaire. Musk
  became one of Trump’s &lt;a href=&quot;https://wapo.st/412xKmy&quot;&gt;largest contributors&lt;/a&gt; and has
  been warping Twitter further and further right-wing and &lt;a
    href=&quot;https://bsky.app/profile/did:plc:c7ozmxoc5b2ky4iam2o36uic/post/3lb3bewyxj22p&quot;
  &gt;away from free-speech principles&lt;/a&gt;. Peter Thiel, a billionaire early investor of Meta,
  believes that &lt;a
    href=&quot;https://www.cato-unbound.org/2009/04/13/peter-thiel/education-libertarian/&quot;
  &gt;freedom and democracy are incompatible&lt;/a&gt;, and is also an
  &lt;a
    href=&quot;https://www.motherjones.com/politics/2024/07/peter-thiel-donald-trump-vance-hulk-hogan/&quot;
  &gt;early contributor&lt;/a&gt; to Vice President-elect J.D. Vance. Rupert Murdoch is a billionaire
  that owns a &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_assets_owned_by_News_Corp&quot;
  &gt;right-wing entertainment machine&lt;/a&gt; that has done more to create disturbing information
  silos than any other entity.
&lt;/p&gt;
&lt;p&gt;
  I doubt these people get together to conspire. Surely their egos can only stand each
  other’s company for a short time. But they all individually want the same thing: to
  further cement their status as oligarchs. The only thing that stands in their way is a
  well informed populace with an agreement on basic facts — so they seek to destroy even
  that.
&lt;/p&gt;
&lt;article-photo
  id=&quot;base-of-the-genesee-arch-bridge-letchworth-state-park&quot;
  placement=&quot;right&quot;
  class=&quot;wdam8yees&quot;
&gt;
  &lt;figure&gt;
    &lt;a
      href=&quot;https://limulus.net/photos/base-of-the-genesee-arch-bridge-letchworth-state-park/&quot;
    &gt;&lt;pho-picture placement=&quot;right&quot; class=&quot;wdam8yees&quot;&gt;
        &lt;blur-hash blurhash=&quot;LFG8o]yV?tnO}n%fS0xGx]oynPWA&quot; width=&quot;3000&quot; height=&quot;4000&quot;&gt;
          &lt;picture&gt;
            &lt;source
              srcset=&quot;https://pho.limulus.net/lnvAq0F9/lg.IxoJIjJL.avif 3000w, https://pho.limulus.net/lnvAq0F9/md.1P0d1_Mv.avif 1500w&quot;
              type=&quot;image/avif&quot;
              width=&quot;3000&quot;
              height=&quot;4000&quot;
            &gt;
            &lt;source
              srcset=&quot;https://pho.limulus.net/lnvAq0F9/lg.Wqu2RASy.jpeg 3000w, https://pho.limulus.net/lnvAq0F9/md.MDvFdUnz.jpeg 1500w&quot;
              type=&quot;image/jpeg&quot;
              width=&quot;3000&quot;
              height=&quot;4000&quot;
            &gt;
            &lt;img
              async=&quot;&quot;
              crossorigin=&quot;anonymous&quot;
              loading=&quot;lazy&quot;
              decoding=&quot;async&quot;
              src=&quot;https://pho.limulus.net/lnvAq0F9/lg.Wqu2RASy.jpeg&quot;
              srcset=&quot;https://pho.limulus.net/lnvAq0F9/lg.Wqu2RASy.jpeg 3000w, https://pho.limulus.net/lnvAq0F9/md.MDvFdUnz.jpeg 1500w&quot;
              alt=&quot;A telephoto shot of the eastern base of the Genesee Arch Bridge (also known as the Portage Viaduct). The red steel arch extends beyond the frame. The base is embedded into an area likely excavated out of the gorge’s wall. Various stairs and catwalks line the sides of the base.&quot;
              width=&quot;3000&quot;
              height=&quot;4000&quot;
              placement=&quot;right&quot;
              class=&quot;wdam8yees&quot;
            &gt;
          &lt;/picture&gt;
        &lt;/blur-hash&gt;
        &lt;p&gt;&lt;/p&gt;&lt;/pho-picture&gt;&lt;/a&gt;
    &lt;figcaption&gt;
      &lt;span&gt;Base of the Genesee Arch Bridge, Letchworth State Park&lt;/span&gt;
    &lt;/figcaption&gt;
    &lt;p&gt;&lt;/p&gt;
  &lt;/figure&gt;
&lt;/article-photo&gt;
&lt;h2 id=&quot;a-fair-and-famous-stream&quot; tabindex=&quot;-1&quot;&gt;A Fair and Famous Stream&lt;/h2&gt;
&lt;p&gt;
  Finally the glaciers receded. The land, no longer being crushed by heavy ice sheets,
  &lt;a href=&quot;https://en.wikipedia.org/wiki/Post-glacial_rebound&quot;&gt;rebounded&lt;/a&gt;. New valleys
  were revealed. The Genesee River — which flows north towards Rochester and drains into
  Lake Ontario — found a new route. Over thousands of years this new route carved Letchworth
  Gorge, home to a number of impressive waterfalls. This is still new territory for &lt;a
    href=&quot;https://rbscp.lib.rochester.edu/1816&quot;
  &gt;our own dear Genesee&lt;/a&gt;.
&lt;/p&gt;
&lt;h2 id=&quot;rebuilding-limulus.net&quot; tabindex=&quot;-1&quot;&gt;Rebuilding limulus.net&lt;/h2&gt;
&lt;p&gt;
  What can I do in the face of this threat to my values? Well, there is only so much any one
  of us can do to solve the problems of the world. But one small thing I think I can do is
  rebuild this space and start regularly creating things to put on it. My hope here is to
  help find ways to make “platforms” less necessary, while also promoting ideas like
  subscribing to
  &lt;a href=&quot;https://aboutfeeds.com/&quot;&gt;web feeds&lt;/a&gt; instead of following on LinkedIn,
  subscribing to a YouTube channel, or subscribing to &lt;a
    href=&quot;https://www.anildash.com/2024/11/19/dont-call-it-a-substack/&quot;
  &gt;a Substack&lt;/a&gt;. Part of this will — somewhat ironically — involve posting limulus.net
  links to some of these platforms.
&lt;/p&gt;
&lt;p&gt;
  This is not an approach that I came up with, nor is it even a new idea. It’s known as
  &lt;a href=&quot;https://indieweb.org/POSSE&quot;&gt;POSSE&lt;/a&gt;: “Publish (on your) Own Site, Syndicate
  Elsewhere.” Shamefully, I have only recently come to know about the &lt;a
    href=&quot;https://indieweb.org&quot;
  &gt;IndieWeb&lt;/a&gt; movement that has been championing this approach.
&lt;/p&gt;
&lt;p&gt;
  A more technical article about how I am building this site with &lt;a
    href=&quot;https://11ty.dev/&quot;
  &gt;Eleventy&lt;/a&gt; and other tools will inevitably come later. But working on this article and
  working through some of the remaining technical challenges has helped alleviate some of my
  election-outcome despair. I am back to feeling like there is some hope. A lot of that hope
  is coming from the potential the web has to make a rebound after a capitalism-induced
  ice-age.
&lt;/p&gt;
</description>
      <pubDate>Sun, 24 Nov 2024 18:35:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/feed/the-rebound/</guid>
    </item>
    <item>
      <title>The Twist You Possibly Expected: Rust</title>
      <link>https://limulus.net/penumbra/journal/rust/</link>
      <description>&lt;p&gt;
  I’ve &lt;a href=&quot;https://limulus.net/penumbra/journal/tuples/#looking-ahead&quot;&gt;previously&lt;/a&gt;
  &lt;a
    href=&quot;https://limulus.net/penumbra/journal/canvas-and-matrices/#implementing-the-canvas-class&quot;
  &gt;mentioned&lt;/a&gt; my intention to eventually switch from targeting JavaScript to targeting &lt;a
    href=&quot;https://webassembly.org/&quot;
  &gt;WebAssembly&lt;/a&gt;. Well, I’ve done it! I went through and reimplemented everything I have
  done so far to target WebAssembly using &lt;a
    href=&quot;https://en.wikipedia.org/wiki/Single_instruction,_multiple_data&quot;
  &gt;SIMD&lt;/a&gt; instructions. Here’s the previous
  &lt;a href=&quot;https://limulus.net/penumbra/journal/ray-sphere-interactions/#demo&quot;
  &gt;sphere’s shadow demo&lt;/a&gt; alongside the new WebAssembly version:
&lt;/p&gt;
&lt;div&gt;
  &lt;sphere-shadow-js-vs-wasm&gt;
    &lt;noscript&gt;
      &lt;p&gt;&lt;em&gt;View this dynamic content on the website with JavaScript enabled.&lt;/em&gt;&lt;/p&gt;
    &lt;/noscript&gt;
    &lt;script&gt;
    // Ensure that syndicated copies of this content don’t show a &quot;Loading…&quot; message. This is
  // necessary because some feed readers try to execute JavaScript.
  const firstPartyHosts = [&#39;limulus.net&#39;, &#39;localhost&#39;, &#39;127.0.0.1&#39;]
  if (firstPartyHosts.some((host) =&gt; window.location.host.includes(host))) {
    document.write(&#39;&lt;p&gt;&lt;em&gt;Loading…&lt;/em&gt;&lt;/p&gt;&#39;)
  } else {
    document.write(&#39;&lt;p&gt;&lt;em&gt;View this dynamic content on the website with JavaScript enabled.&lt;/em&gt;&lt;/p&gt;&#39;)
  }
    &lt;/script&gt;
  &lt;/sphere-shadow-js-vs-wasm&gt;
&lt;/div&gt;
&lt;p&gt;
  Click and drag (or touch and drag) to change the position of the light source, and thus
  change the shape of the sphere’s shadow. Change the resolution via the dropdown to observe
  the effect on render times.
&lt;/p&gt;
&lt;h2 id=&quot;eschewing-assemblyscript&quot; tabindex=&quot;-1&quot;&gt;Eschewing AssemblyScript&lt;/h2&gt;
&lt;p&gt;
  I previously mentioned that my plan was to rewrite using &lt;a
    href=&quot;https://www.assemblyscript.org/&quot;
  &gt;AssemblyScript&lt;/a&gt;, which is a TypeScript based language that compiles directly to
  WebAssembly. This seemed really promising to me. My goal with this project was not to
  learn a new programming language but to learn about ray tracing and maybe do something fun
  with it.
&lt;/p&gt;
&lt;p&gt;
  Unfortunately, as I began to look more seriously into AssemblyScript I started to have my
  doubts about it. I briefly joined the AssemblyScript Discord server and it became apparent
  that established WebAssembly features like threads were not going to be implemented any
  time soon — seemingly because the authors have become disenchanted with one or more of the
  W3C working groups of which they were once a part of. In a &lt;a
    href=&quot;https://www.assemblyscript.org/standards-objections.html&quot;
  &gt;long and somewhat inscrutable manifesto&lt;/a&gt; they list their objections, offenses, and
  demands. I can’t discount that they were mistreated but I nevertheless found it an
  off-putting read. They may well have some valid points — after all it is easy to be
  sympathetic about ensuring WebAssembly interoperates well with the web — but I can’t shake
  the feeling that maybe the WebAssembly standard will be better without their participation
  for a time.
&lt;/p&gt;
&lt;p&gt;
  Once I took AssemblyScript off the top of the list of possibilities I looked for
  alternatives, but ultimately &lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust&lt;/a&gt; was the obvious
  choice. It’s a language I have wanted to learn anyway.
&lt;/p&gt;
&lt;h2 id=&quot;adopting-rust&quot; tabindex=&quot;-1&quot;&gt;Adopting Rust&lt;/h2&gt;
&lt;p&gt;
  I have previously spent a little bit of time playing with Rust, but this was certainly a
  more thorough experience. It was helpful to understand that since I was building something
  that would solely target WebAssembly I could rely on &lt;a
    href=&quot;https://github.com/rustwasm/wasm-pack&quot;
  &gt;wasm-pack&lt;/a&gt; to take care of a lot of the build details.
&lt;/p&gt;
&lt;h3 id=&quot;tests&quot; tabindex=&quot;-1&quot;&gt;Tests&lt;/h3&gt;
&lt;p&gt;
  Getting tests working was also made pretty simple thanks to wasm-pack and &lt;a
    href=&quot;https://github.com/rustwasm/wasm-bindgen&quot;
  &gt;wasm-bindgen&lt;/a&gt;. The standard way of writing tests in Rust is to put the tests in the
  same file as the code under test, inside a &lt;code&gt;tests&lt;/code&gt; module. Here’s an example
  from &lt;a
    href=&quot;https://github.com/limulus/penumbra/blob/3558cf6/wasm/src/ray.rs#L27-L40&quot;
  &gt;&lt;code&gt;ray.rs&lt;/code&gt;&lt;/a&gt;:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;#[cfg(test)]&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;mod&lt;/span&gt; tests {
  &lt;span class=&quot;hljs-keyword&quot;&gt;use&lt;/span&gt; super::*;
  &lt;span class=&quot;hljs-keyword&quot;&gt;use&lt;/span&gt; wasm_bindgen_test::*;

  &lt;span class=&quot;hljs-meta&quot;&gt;#[wasm_bindgen_test]&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;creating_and_querying_a_ray&lt;/span&gt;() {
    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;origin&lt;/span&gt; = Tuple::&lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;point&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;1.0&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;2.0&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;3.0&lt;/span&gt;);
    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;direction&lt;/span&gt; = Tuple::&lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;vector&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;4.0&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;5.0&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;6.0&lt;/span&gt;);
    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;r&lt;/span&gt; = Ray::&lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;new&lt;/span&gt;(origin, direction);

    &lt;span class=&quot;hljs-built_in&quot;&gt;assert_eq!&lt;/span&gt;(r.origin, origin);
    &lt;span class=&quot;hljs-built_in&quot;&gt;assert_eq!&lt;/span&gt;(r.direction, direction);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  One downside to having all these tests in Rust means there is no obvious way to get them
  running on this site. As a result, I’ve taken down the test page that allowed you to run
  the JavaScript tests in your browser. I might explore that at some point, but its not a
  priority for me right now.
&lt;/p&gt;
&lt;h3 id=&quot;using-simd-instructions&quot; tabindex=&quot;-1&quot;&gt;Using SIMD Instructions&lt;/h3&gt;
&lt;p&gt;
  If you’re not familiar with “Single Instruction, Multiple Data,” it is a category of
  instruction sets CPUs implement to speed up calculations where you need to perform the
  same series of operations over different sets of variables. This comes in handy for things
  like matrix math. If you are old enough, you may even remember when SIMD instruction sets
  began to be added to processors: &lt;a
    href=&quot;https://en.wikipedia.org/wiki/MMX_(instruction_set)&quot;
  &gt;MMX&lt;/a&gt; on Intel and &lt;a href=&quot;https://en.wikipedia.org/wiki/AltiVec&quot;&gt;AltiVec&lt;/a&gt; for
  PowerPC. These days the common SIMD instruction sets are &lt;a
    href=&quot;https://en.wikipedia.org/wiki/Advanced_Vector_Extensions&quot;
  &gt;AVX&lt;/a&gt; on Intel or AMD processors and &lt;a
    href=&quot;https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(Neon)&quot;
  &gt;Neon&lt;/a&gt; on ARM processors.
&lt;/p&gt;
&lt;p&gt;
  Of course, WebAssembly is a virtual machine. You can’t just throw AVX and Neon
  instructions in the WASM file. Instead, WebAssembly has defined SIMD instructions that get
  compiled into the SIMD instructions for whatever architecture the browser is running on.
  In Rust these are exposed as compiler intrinsics in the &lt;a
    href=&quot;https://doc.rust-lang.org/core/arch/wasm32/index.html&quot;
  &gt;&lt;code&gt;std::arch::wasm32&lt;/code&gt;&lt;/a&gt; module.
&lt;/p&gt;
&lt;p&gt;
  WebAssembly’s SIMD instructions are all fixed-width, operating on 128-bit wide operands.
  So for example there’s &lt;code&gt;u8x16&lt;/code&gt; instructions for addition, subtraction,
  multiplication, division, comparisons, etc. that operate on 16 8-bit unsigned integers
  packed into 128-bit wide registers. Likewise, there are instructions for &lt;code
  &gt;i8x16&lt;/code&gt; for signed 8-bit integers and
  &lt;code&gt;f32x4&lt;/code&gt; for 32-bit floating point values.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a
      href=&quot;https://limulus.net/penumbra/journal/rust/#fn1&quot;
      id=&quot;fnref1&quot;
    &gt;[1]&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;
&lt;p&gt;
  Here’s an &lt;a
    href=&quot;https://github.com/limulus/penumbra/blob/3558cf6/wasm/src/matrix.rs#L222-L238&quot;
  &gt;excerpt from &lt;code&gt;matrix.rs&lt;/code&gt;&lt;/a&gt; showing the multiplication operator
  implementation for
  &lt;code&gt;Tuple&lt;/code&gt; and &lt;code&gt;Matrix4&lt;/code&gt;.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Mul&lt;/span&gt;&amp;lt;Tuple&amp;gt; &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; &amp;amp;Matrix4 {
  &lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Output&lt;/span&gt; = Tuple;

  &lt;span class=&quot;hljs-keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;mul&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;self&lt;/span&gt;, other: Tuple) &lt;span class=&quot;hljs-punctuation&quot;&gt;-&amp;gt;&lt;/span&gt; Tuple {
    &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;mut &lt;/span&gt;&lt;span class=&quot;hljs-variable&quot;&gt;sum&lt;/span&gt; = &lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;f32x4_splat&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;0.0&lt;/span&gt;);
    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;..&lt;span class=&quot;hljs-number&quot;&gt;4&lt;/span&gt; {
      sum = &lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;f32x4_add&lt;/span&gt;(
        sum,
        &lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;f32x4_mul&lt;/span&gt;(
          &lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;f32x4_splat&lt;/span&gt;(other.&lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;get&lt;/span&gt;(i)),
          &lt;span class=&quot;hljs-keyword&quot;&gt;self&lt;/span&gt;.&lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;col_v128&lt;/span&gt;(i),
        )
      );
    }
    Tuple::&lt;span class=&quot;hljs-title function_ invoke__&quot;&gt;from_v128&lt;/span&gt;(sum)
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  Now that I have implemented everything using explicit WebAssembly SIMD intrinsics, I am
  wondering if I should have looked more closely at the still-experimental &lt;a
    href=&quot;https://doc.rust-lang.org/std/simd/&quot;
  &gt;portable SIMD&lt;/a&gt;
  module. The main benefit is that I could also target non-SIMD WebAssembly and compare the
  performance. I could probably also more easily make use of WebAssembly’s &lt;a
    href=&quot;https://github.com/WebAssembly/relaxed-simd/blob/95dc80e/proposals/relaxed-simd/Overview.md&quot;
  &gt;relaxed SIMD&lt;/a&gt;
  which includes instructions like &lt;a
    href=&quot;https://doc.rust-lang.org/stable/core/arch/wasm32/fn.f32x4_relaxed_madd.html&quot;
  &gt;&lt;code&gt;f32x4_relaxed_madd&lt;/code&gt;&lt;/a&gt; that would likely speed up matrix multiplication
  noticeably.
&lt;/p&gt;
&lt;h4 id=&quot;f32x4_relaxed_madd&quot; tabindex=&quot;-1&quot;&gt;Speaking of &lt;code&gt;f32x4_relaxed_madd&lt;/code&gt;…&lt;/h4&gt;
&lt;p&gt;
  …I’m curious how switching the above &lt;code&gt;Tuple * Matrix4&lt;/code&gt; implementation to it
  would perform. So I spent around two hours trying to get it to work, to no avail. I think
  the problem is that &lt;a href=&quot;https://github.com/rustwasm/walrus&quot;&gt;walrus&lt;/a&gt;, which is used
  by &lt;code&gt;wasm-bindgen&lt;/code&gt;, does not yet seem to support the relaxed SIMD instructions.
  It throws this error when it hits the &lt;code&gt;f32x4_relaxed_madd&lt;/code&gt; instruction:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs&quot;&gt;Error: failed to deserialize wasm module

Caused by:
    0: failed to parse code section
    1: Unknown 0xfd subopcode: 0x105 (at offset 312376)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  Maybe this should not be too much of a surprise, considering Relaxed SIMD doesn’t quite
  yet have wide support. It’s shipping in Chrome currently and behind a feature flag in
  Firefox. It looks like Safari may be getting it soon based on &lt;a
    href=&quot;https://commits.webkit.org/265324@main&quot;
  &gt;this WebKit commit&lt;/a&gt; from 2023, although it’s not a feature flag in &lt;a
    href=&quot;https://developer.apple.com/safari/technology-preview/&quot;
  &gt;Safari Technology Preview&lt;/a&gt;.
&lt;/p&gt;
&lt;h2
  id=&quot;wait%2C-what%E2%80%99s-that-weird-artifact-in-the-top-left-of-%3Csphere-shadow-wasm%3E%3F&quot;
  tabindex=&quot;-1&quot;
&gt;
  Wait, What’s That Weird Artifact in the Top Left of &amp;lt;sphere-shadow-wasm&amp;gt;?
&lt;/h2&gt;
&lt;p&gt;
  I’m actually not sure! There’s 8 or pixels that are transparent, so if you are viewing the
  site in dark mode they will look black, and if in light mode they will look white. I
  haven’t spent too much time trying to figure it out, but I must be doing something wrong
  when copying the image data from WASM world to JS world, or thereabouts.
&lt;/p&gt;
&lt;h2 id=&quot;detours&quot; tabindex=&quot;-1&quot;&gt;Detours&lt;/h2&gt;
&lt;p&gt;
  This entry took a while to get completed thanks to various detours I took. The bulk of the
  Rust work was actually done pretty quickly. It maybe took a month or less. But I took a
  number of detours to work on this site, including adding support for serving videos and
  breaking apart the repository into three repositories. There’s now the &lt;a
    href=&quot;https://github.com/limulus/penumbra&quot;
  &gt;penumbra&lt;/a&gt; repo for the core library, &lt;a href=&quot;https://github.com/limulus/penumbra-www&quot;
  &gt;penumbra-www&lt;/a&gt; for this website, and &lt;a href=&quot;https://github.com/limulus/touch-pad&quot;
  &gt;touch-pad&lt;/a&gt; which is now available as an independent &lt;a
    href=&quot;https://www.npmjs.com/package/touch-pad&quot;
  &gt;npm package&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
  &lt;em&gt;Update 2024-11-20: The &lt;code&gt;penumbra-www&lt;/code&gt; repo has been promoted to the main
    repo for all of limulus.net! It’s been renamed to &lt;a
      href=&quot;https://github.com/limulus/limulus-dot-net&quot;
    &gt;limulus-dot-net&lt;/a&gt;.&lt;/em&gt;
&lt;/p&gt;
&lt;h2 id=&quot;what%E2%80%99s-next%3F&quot; tabindex=&quot;-1&quot;&gt;What’s Next?&lt;/h2&gt;
&lt;p&gt;
  With so much time spent on the above detours I’m looking forward to finally starting the
  next chapter of the book, which is “Light and Shading”. I’ll be screen recording as I work
  on it, so I might also produce some kind of video, likely focused on whatever demo I
  create.
&lt;/p&gt;
&lt;section class=&quot;footnotes section-separator&quot;&gt;
  &lt;ol class=&quot;footnotes-list&quot;&gt;
    &lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;
      &lt;p&gt;
        In addition to &lt;code&gt;u8x16&lt;/code&gt;, &lt;code&gt;i8x16&lt;/code&gt;, &lt;code&gt;f32x4&lt;/code&gt;, and &lt;code
        &gt;f64x2&lt;/code&gt; there are also instructions for
        &lt;code&gt;u16x8&lt;/code&gt;, &lt;code&gt;i16x8&lt;/code&gt;, &lt;code&gt;u32x4&lt;/code&gt;, &lt;code&gt;i32x4&lt;/code&gt;,
        &lt;code&gt;u64x2&lt;/code&gt;, and &lt;code&gt;i64x2&lt;/code&gt;. With all the ways you might want to
        slice 128-bit wide operands and all the different operations you want to do for each
        way of slicing, this makes for a lot of instructions! &lt;a
          href=&quot;https://limulus.net/penumbra/journal/rust/#fnref1&quot;
          class=&quot;footnote-backref&quot;
        &gt;↩︎&lt;/a&gt;
      &lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Tue, 04 Jun 2024 20:20:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/penumbra/journal/rust/</guid>
    </item>
    <item>
      <title>How I Am Using GitHub Copilot</title>
      <link>https://limulus.net/penumbra/journal/github-copilot/</link>
      <description>&lt;p&gt;
  Above is a video I produced to demo &lt;a href=&quot;https://copilot.github.com&quot;
  &gt;GitHub Copilot&lt;/a&gt; to my coworkers. If you haven’t yet explored using a Large Language
  Model to you help you code, it is worth a watch. I screen-recorded myself developing an
  optimization for &lt;a href=&quot;https://limulus.net/&quot;&gt;Penumbra&lt;/a&gt; (this project) and edited it
  down to about 6 minutes.
&lt;/p&gt;
&lt;p&gt;
  I use Copilot in other ways not covered in the video. It’s clearly been trained on other
  &lt;a href=&quot;https://pragprog.com/titles/jbtracer/the-ray-tracer-challenge/&quot;
  &gt;Ray Tracer Challenge&lt;/a&gt; implementations so it very quickly autocompletes tests with all
  the exact values. This has saved me a bunch of mindless typing. It also autocompletes
  production code that satisfies the tests, which is often less helpful for this project
  since I usually want to spend some time thinking about how to implement these things. But
  sometimes I turn it back on to get suggestions that prompt me to consider a different and
  potentially better solution.
&lt;/p&gt;
&lt;p&gt;
  If you’ve read my previous posts you’ll notice that I’ve switched to &lt;a
    href=&quot;https://www.rust-lang.org&quot;
  &gt;Rust&lt;/a&gt; targeting
  &lt;a href=&quot;https://webassembly.org&quot;&gt;WebAssembly&lt;/a&gt;. There’s a story behind that! But it
  will have to wait for a future post.
&lt;/p&gt;
&lt;p&gt;
  Making this video was a lot of fun! A little less fun was navigating how to host video on
  this site without introducing a dependency on a third-party. There’s a good reason why
  just about everyone uploads to YouTube — doing this reasonably well is not easy. This may
  wind up needing to be a journal entry or even video of its own.
&lt;/p&gt;
</description>
      <pubDate>Sat, 23 Mar 2024 22:15:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/penumbra/journal/github-copilot/</guid>
    </item>
    <item>
      <title>Chapter 5: Ray-Sphere Interactions</title>
      <link>https://limulus.net/penumbra/journal/ray-sphere-interactions/</link>
      <description>&lt;p&gt;
  In chapter 5 of &lt;a href=&quot;https://pragprog.com/titles/jbtracer/the-ray-tracer-challenge/&quot;
  &gt;The Ray Tracer Challenge&lt;/a&gt; you finally get to implement something that starts to
  resemble a ray tracer. You implement ray, sphere, and intersection related functions and
  the exercise at the end ties it all together to create an image.
&lt;/p&gt;
&lt;h2 id=&quot;finding-intersections&quot; tabindex=&quot;-1&quot;&gt;Finding Intersections&lt;/h2&gt;
&lt;p&gt;
  The book does not go into the details of the math for how to determine the intersection
  points of a ray and sphere. I’m glad for that, but it bugged me that I do not have an
  intuitive understanding of &lt;em&gt;why&lt;/em&gt; &lt;a
    href=&quot;https://github.com/limulus/penumbra/blob/1fee85dad2f656c6d028a74d957b2129c0476f34/src/lib/sphere.ts#L27-L51&quot;
  &gt;this intersect method&lt;/a&gt; works.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Sphere&lt;/span&gt; {
  &lt;span class=&quot;hljs-title function_&quot;&gt;intersect&lt;/span&gt;(&lt;span class=&quot;hljs-attr&quot;&gt;ray&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;Ray&lt;/span&gt;): &lt;span class=&quot;hljs-title class_&quot;&gt;IntersectionCollection&lt;/span&gt; {
    &lt;span class=&quot;hljs-comment&quot;&gt;// Transform the ray into object space&lt;/span&gt;
    ray = ray.&lt;span class=&quot;hljs-title function_&quot;&gt;transform&lt;/span&gt;(&lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;transformInverse&lt;/span&gt;)

    &lt;span class=&quot;hljs-comment&quot;&gt;// Vector from sphere origin to the ray origin&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; sphereToRayVec = ray.&lt;span class=&quot;hljs-property&quot;&gt;origin&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;sub&lt;/span&gt;(origin)

    &lt;span class=&quot;hljs-comment&quot;&gt;// Supporting characters to determine the discriminant and intersection&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; a = ray.&lt;span class=&quot;hljs-property&quot;&gt;direction&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;dot&lt;/span&gt;(ray.&lt;span class=&quot;hljs-property&quot;&gt;direction&lt;/span&gt;)
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; b = &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt; * ray.&lt;span class=&quot;hljs-property&quot;&gt;direction&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;dot&lt;/span&gt;(sphereToRayVec)
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; c = sphereToRayVec.&lt;span class=&quot;hljs-title function_&quot;&gt;dot&lt;/span&gt;(sphereToRayVec) - &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;

    &lt;span class=&quot;hljs-comment&quot;&gt;// Discriminant does not intersect sphere if it is negative&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; discriminant = b ** &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt; - &lt;span class=&quot;hljs-number&quot;&gt;4&lt;/span&gt; * a * c
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (discriminant &amp;lt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;) &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;IntersectionCollection&lt;/span&gt;()

    &lt;span class=&quot;hljs-comment&quot;&gt;// Calculate the intersection points&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; sqrtDiscriminant = &lt;span class=&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;sqrt&lt;/span&gt;(discriminant)
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; t1 = (-b - sqrtDiscriminant) / (&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt; * a)
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; t2 = (-b + sqrtDiscriminant) / (&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt; * a)
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;IntersectionCollection&lt;/span&gt;(
      &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Intersection&lt;/span&gt;(t1, &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;),
      &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Intersection&lt;/span&gt;(t2, &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;)
    )
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  The book does suggest some online resources for an explanation of the math at work. I took
  some time to read through &lt;a
    href=&quot;https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection.html&quot;
  &gt;this one&lt;/a&gt;. It includes two solutions: a geometric solution and an analytic solution.
  The geometric solution made sense to me but the analytic solution less so. Still — despite
  an error&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a
      href=&quot;https://limulus.net/penumbra/journal/ray-sphere-interactions/#fn1&quot;
      id=&quot;fnref1&quot;
    &gt;[1]&lt;/a&gt;&lt;/sup&gt; in that explanation — it did make some sense. One thing that helped was
  realizing that the &lt;code&gt;discriminant&lt;/code&gt; being negative means there is no intersection
  because that would require taking the square root of a negative number.
&lt;/p&gt;
&lt;p&gt;
  The solution the book provides and I implemented above is the analytic solution. I would
  have a deeper understanding of it if it were the geometric solution, but at least I now
  have a better-than-tenuous idea of why this code works.
&lt;/p&gt;
&lt;h2 id=&quot;demo&quot; tabindex=&quot;-1&quot;&gt;The Demo: &lt;code&gt;&amp;lt;sphere-shadow&amp;gt;&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;
  The exercise at the end of the chapter is to render the shadow of a sphere by casting rays
  from a light source onto a “wall”. I’ve implemented that here, with the addition that you
  can change the position of the light source by dragging from the element.
&lt;/p&gt;
&lt;div&gt;
  &lt;sphere-shadow-demo&gt;
    &lt;noscript&gt;
      &lt;p&gt;&lt;em&gt;View this dynamic content on the website with JavaScript enabled.&lt;/em&gt;&lt;/p&gt;
    &lt;/noscript&gt;
    &lt;script&gt;
    // Ensure that syndicated copies of this content don’t show a &quot;Loading…&quot; message. This is
  // necessary because some feed readers try to execute JavaScript.
  const firstPartyHosts = [&#39;limulus.net&#39;, &#39;localhost&#39;, &#39;127.0.0.1&#39;]
  if (firstPartyHosts.some((host) =&gt; window.location.host.includes(host))) {
    document.write(&#39;&lt;p&gt;&lt;em&gt;Loading…&lt;/em&gt;&lt;/p&gt;&#39;)
  } else {
    document.write(&#39;&lt;p&gt;&lt;em&gt;View this dynamic content on the website with JavaScript enabled.&lt;/em&gt;&lt;/p&gt;&#39;)
  }
    &lt;/script&gt;
  &lt;/sphere-shadow-demo&gt;
&lt;/div&gt;
&lt;p&gt;
  I don’t normally test drive my demo code since it is more exploratory fun than writing
  code I intend to reuse. But I figured I would want to make use of the dragging interaction
  again, so I &lt;a
    href=&quot;https://github.com/limulus/penumbra/blob/93ee15212eb71ad186f072d8c38e3d8a1f3b8500/src/lib/ui/touch-pad.spec.ts&quot;
  &gt;test-drove the creation&lt;/a&gt; of a &lt;a
    href=&quot;https://github.com/limulus/penumbra/blob/93ee15212eb71ad186f072d8c38e3d8a1f3b8500/src/lib/ui/touch-pad.ts&quot;
  &gt;TouchPad class&lt;/a&gt; to track all the mouse and touch events on (and off) of the element
  and emit only the needed move events. At some point I should add keyboard support to it as
  well.
&lt;/p&gt;
&lt;p&gt;
  Other than that there is not much new from the web technology side compared to the &lt;a
    href=&quot;https://limulus.net/penumbra/journal/canvas-and-matrices/&quot;
  &gt;previous demo&lt;/a&gt;. Rendering of the canvas still happens in a single web worker.
&lt;/p&gt;
&lt;h2 id=&quot;what%E2%80%99s-next&quot; tabindex=&quot;-1&quot;&gt;What’s Next&lt;/h2&gt;
&lt;p&gt;
  In the demo I included an output to show the render time of the last frame. I get around
  5.5ms in Chrome on my Mac Studio with an M2 Max. Firefox gets around 18.5ms and Safari
  around 9ms. I find this performance a little disappointing considering I feel like I have
  optimized things as much as I reasonably can. It makes me wonder if I should skip to
  targeting Web Assembly earlier than I was planning. I would like to keep the demos
  interactive in a real-time sort of way. Parallelization will help, but only so much on
  older devices with fewer CPU cores. Maybe now is the time…
&lt;/p&gt;
&lt;section class=&quot;footnotes section-separator&quot;&gt;
  &lt;ol class=&quot;footnotes-list&quot;&gt;
    &lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;
      &lt;p&gt;
        At the time of writing this the issue with that page is that in the “Analytic
        Solution” section “equation 5” is a repeat of “equation 4”. It should actually be
        the
        &lt;a href=&quot;https://en.m.wikipedia.org/wiki/Quadratic_formula&quot;&gt;quadratic formula&lt;/a&gt;:
      &lt;/p&gt;
      &lt;math display=&quot;block&quot;&gt;
        &lt;mi&gt;x&lt;/mi&gt;
        &lt;mo&gt; = &lt;/mo&gt;
        &lt;mfrac&gt;
          &lt;mrow&gt;
            &lt;mo&gt;−&lt;/mo&gt;&lt;mi&gt;b&lt;/mi&gt;
            &lt;mo&gt;±&lt;/mo&gt;
            &lt;msqrt&gt;
              &lt;msup&gt;&lt;mi&gt;b&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;
              &lt;mo&gt;−&lt;/mo&gt;
              &lt;mn&gt;4&lt;/mn&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mi&gt;c&lt;/mi&gt;
            &lt;/msqrt&gt;
          &lt;/mrow&gt;
          &lt;mrow&gt;
            &lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;a&lt;/mi&gt;
          &lt;/mrow&gt;
        &lt;/mfrac&gt;
      &lt;/math&gt;
      &lt;p&gt;
        Or, with the discriminant represented as
        &lt;math display=&quot;inline&quot;&gt;&lt;mi mathvariant=&quot;normal&quot;&gt;Δ&lt;/mi&gt;&lt;/math&gt;:
      &lt;/p&gt;
      &lt;math display=&quot;block&quot;&gt;
        &lt;mi&gt;x&lt;/mi&gt;
        &lt;mo&gt; = &lt;/mo&gt;
        &lt;mfrac&gt;
          &lt;mrow&gt;
            &lt;mo&gt;−&lt;/mo&gt;&lt;mi&gt;b&lt;/mi&gt;
            &lt;mo&gt;±&lt;/mo&gt;
            &lt;msqrt&gt;
              &lt;mi mathvariant=&quot;normal&quot;&gt;Δ&lt;/mi&gt;
            &lt;/msqrt&gt;
          &lt;/mrow&gt;
          &lt;mrow&gt;
            &lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;a&lt;/mi&gt;
          &lt;/mrow&gt;
        &lt;/mfrac&gt;
      &lt;/math&gt;
      &lt;p&gt;
        If you decide to dig this deep hopefully the above can save you the intense head
        scratching that I went through. &lt;a
          href=&quot;https://limulus.net/penumbra/journal/ray-sphere-interactions/#fnref1&quot;
          class=&quot;footnote-backref&quot;
        &gt;↩︎&lt;/a&gt;
      &lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Fri, 29 Dec 2023 21:00:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/penumbra/journal/ray-sphere-interactions/</guid>
    </item>
    <item>
      <title>Chapters 2–4: Canvas and Matrices</title>
      <link>https://limulus.net/penumbra/journal/canvas-and-matrices/</link>
      <description>&lt;p&gt;
  In the &lt;a href=&quot;https://limulus.net/penumbra/journal/tuples/&quot;&gt;previous post&lt;/a&gt; I went a
  little further than the exercise at the end of the chapter asked for and created a web
  component that exercised my tuple implementation and included animation of the projectile.
  As it turns out, this wound up being very similar to the exercise at the end of chapter 2,
  which is about implementing a canvas. So I decided to skip that exercise and continue onto
  the next two chapters which walk you through implementing various matrix math operations.
&lt;/p&gt;
&lt;h2 id=&quot;implementing-the-canvas-class&quot; tabindex=&quot;-1&quot;&gt;Implementing the Canvas Class&lt;/h2&gt;
&lt;p&gt;
  Implementing a canvas class when targeting a web runtime is perhaps unnecessary since
  &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas&quot;&gt;&lt;code
    &gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;&lt;/a&gt; provides a solid 2D canvas JavaScript API. However, the book
  has you implement color functions for tuples containing floats so a canvas that stores
  colors with floats instead of integer values seems like it might be a better bet going
  forward. So I decided to implement my own &lt;a
    href=&quot;https://github.com/limulus/penumbra/blob/d32022d755967a75c3923a156490e18e4315bf17/src/lib/canvas.ts&quot;
  &gt;&lt;code&gt;Canvas&lt;/code&gt; class&lt;/a&gt; backed by a &lt;code&gt;Float32Array&lt;/code&gt;.
&lt;/p&gt;
&lt;p&gt;
  I went with 32-bit floats over the JavaScript-native 64-bit floats since I am still
  planning on porting this to &lt;a href=&quot;https://www.assemblyscript.org&quot;&gt;AssemblyScript&lt;/a&gt; to
  take advantage of the &lt;a
    href=&quot;https://github.com/WebAssembly/simd/blob/a78b98a6899c9e91a13095e560767af6e99d98fd/proposals/simd/SIMD.md&quot;
  &gt;v128 SIMD operations&lt;/a&gt; in WebAssembly. The “128” in “v128” implies you can either have
  an SIMD instruction operate either on 4 32-bit floats or 2 64-bit floats.
  Four-times-faster is better than two-times-faster. And based on a bit of research the
  extra precision is usually not needed or at least easy to avoid needing.
&lt;/p&gt;
&lt;p&gt;
  Using a &lt;a
    href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray&quot;
  &gt;&lt;code&gt;TypedArray&lt;/code&gt;&lt;/a&gt; also opens the doors to backing a canvas with a
  &lt;a
    href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer&quot;
  &gt;&lt;code&gt;SharedArrayBuffer&lt;/code&gt;&lt;/a&gt; or something similar. I can imagine this being useful
  by having the ray tracer running in many WebWorkers, all updating a shared canvas.
&lt;/p&gt;
&lt;p&gt;There’s a bit of a snag with &lt;code&gt;SharedArrayBuffer&lt;/code&gt; however…&lt;/p&gt;
&lt;h3 id=&quot;a-side-quest-to-move-away-from-github-pages&quot; tabindex=&quot;-1&quot;&gt;
  A Side Quest to Move Away From GitHub Pages
&lt;/h3&gt;
&lt;p&gt;
  In response to the &lt;a
    href=&quot;https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)&quot;
  &gt;Spectre vulnerability&lt;/a&gt;, browser vendors updated the &lt;code&gt;SharedArrayBuffer&lt;/code&gt;
  constructor to throw so that it could not be abused until they had a fix. The fix they
  ultimately adopted requires sending &lt;a
    href=&quot;https://hacks.mozilla.org/2020/07/safely-reviving-shared-memory/&quot;
  &gt;two HTTP headers&lt;/a&gt; with your HTML document. Well, you can’t set HTTP headers on GitHub
  Pages, where I was previously hosting this site.
&lt;/p&gt;
&lt;p&gt;
  I always planned on moving this site to my personal website, &lt;a
    href=&quot;https://limulus.net/&quot;
  &gt;limulus.net&lt;/a&gt;. But my setup for limulus.net is very out-of-date. I have a GitHub
  repository for it but deployment is no longer automated. I just manually upload any
  changes to S3. None of the other infrastructure for it like the CloudFront distribution
  has been turned into CloudFormation templates so it’s all just sitting in AWS resources
  without any version control. I wanted to avoid adding to that mess for now by publishing
  to GitHub Pages.
&lt;/p&gt;
&lt;p&gt;
  Even though I know &lt;code&gt;SharedArrayBuffer&lt;/code&gt; may not be how I ultimately choose to
  implement things I also didn’t want to be in the situation where I am forced into
  switching away from GitHub Pages in the middle of the project instead of early on. In the
  (ok, unlikely) event that anyone was subscribed to the &lt;a
    href=&quot;https://limulus.net/feed.xml&quot;
  &gt;RSS feed&lt;/a&gt; setting up redirects on GitHub Pages for that might be tricky. Better to
  just get it out of the way as soon as possible.
&lt;/p&gt;
&lt;p&gt;
  In a bid to get things done though I resisted the urge to write CloudFormation templates
  for everything, so unfortunately I have added to my AWS technical debt. However I did
  spend the time set things up in the new ways AWS recommends: I’m using &lt;a
    href=&quot;https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services&quot;
  &gt;GitHub’s OIDC provider&lt;/a&gt; to get temporary AWS credentials for the GitHub Action that
  publishes this site and I avoided setting up the S3 bucket to use public website mode. I
  learned that to get CloudFront to serve &lt;code&gt;index.html&lt;/code&gt; files for directories
  served from a private S3 origin you need to write a &lt;a
    href=&quot;https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html&quot;
  &gt;CloudFront Function&lt;/a&gt; to rewrite the request. So unfortunately that means I now have a
  tiny bit of code for hosting this site that is not version controlled. But at least now I
  know how to set these things up in a more secure way.
&lt;/p&gt;
&lt;h2 id=&quot;implementing-the-matrix-class&quot; tabindex=&quot;-1&quot;&gt;Implementing the Matrix Class&lt;/h2&gt;
&lt;p&gt;
  There’s only a few things particularly interesting about the implementation of my &lt;a
    href=&quot;https://github.com/limulus/penumbra/blob/d32022d755967a75c3923a156490e18e4315bf17/src/lib/matrix.ts&quot;
  &gt;&lt;code&gt;Matrix&lt;/code&gt;
    class&lt;/a&gt;.
&lt;/p&gt;
&lt;h3 id=&quot;a-surprise-subclass&quot; tabindex=&quot;-1&quot;&gt;A Surprise Subclass&lt;/h3&gt;
&lt;p&gt;
  It likely should not have come as a surprise that the &lt;code&gt;Tuple&lt;/code&gt; implementation
  would need to be treated as a matrix when doing matrix math operations. In fact, tuples
  need to be treated as matrixes of four rows, which is not my intuition about how to
  conceptualize an array of four items. It was seeming like I was going to need special
  handling in my &lt;code&gt;Matrix&lt;/code&gt; class to account for whenever it was passed a &lt;code
  &gt;Tuple&lt;/code&gt; and that felt messy. The solution I landed on was to create a &lt;code
  &gt;TwoDimensionalArray&lt;/code&gt; class that would act as the base class for both the
  &lt;code&gt;Tuple&lt;/code&gt; and &lt;code&gt;Matrix&lt;/code&gt; classes. This way, the Tuple class can
  construct itself with 1 column and 4 rows and the &lt;code&gt;Matrix&lt;/code&gt; class doesn’t have
  to treat &lt;code&gt;Tuple&lt;/code&gt;s as special cases.
&lt;/p&gt;
&lt;p&gt;
  This kind of refactor is definitely where having a robust test suite (in this case
  provided by the book) shines. I had confidence in a relatively substantial refactor
  without any added effort.
&lt;/p&gt;
&lt;h3 id=&quot;chaining-matrix-transformations&quot; tabindex=&quot;-1&quot;&gt;Chaining Matrix Transformations&lt;/h3&gt;
&lt;p&gt;
  While I won’t pretend to understand the reasons why (maybe I knew back when I took linear
  algebra?) if you want a transformation matrix to have multiple transformations you have to
  multiply them in reverse order. In other words, if you want a matrix that you can use to
  do a translation, then a rotation, then a scaling up, you need to first multiply the
  scaling matrix by the rotation matrix, and then the result by the translation matrix. The
  books suggests that you implement a “fluent” API of chainable methods that takes care of
  this for you. For example:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; twoOClock = &lt;span class=&quot;hljs-title class_&quot;&gt;Matrix&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;transformation&lt;/span&gt;()
  .&lt;span class=&quot;hljs-title function_&quot;&gt;translate&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;)
  .&lt;span class=&quot;hljs-title function_&quot;&gt;rotateZ&lt;/span&gt;(-(&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt; / &lt;span class=&quot;hljs-number&quot;&gt;12&lt;/span&gt;) * &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt; * &lt;span class=&quot;hljs-title class_&quot;&gt;Math&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;PI&lt;/span&gt;)
  .&lt;span class=&quot;hljs-title function_&quot;&gt;scale&lt;/span&gt;(clockRadius, -clockRadius, &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  What I’ve seen frequently in JS APIs is that they often require something like a final
  &lt;code&gt;.done()&lt;/code&gt; method to perform the final calculations and produce the end result
  of the chain of operations. However, there is a way around this if you can structure your
  class to have the first attempt to read the values of the returned object do the
  finalization.
&lt;/p&gt;
&lt;p&gt;
  Here’s how that works with my &lt;code&gt;Matrix&lt;/code&gt; class. The following is a selection of
  methods that demonstrate it. The &lt;code&gt;.translate()&lt;/code&gt;, &lt;code&gt;.rotateZ()&lt;/code&gt;, and
  &lt;code&gt;scale()&lt;/code&gt; methods in the example above all call &lt;code&gt;.#pushOperation()&lt;/code&gt;
  to push their operation onto the &lt;code&gt;#operationStack&lt;/code&gt; array and return &lt;code
  &gt;this&lt;/code&gt;.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Matrix&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title class_ inherited__&quot;&gt;TwoDimensionalArray&lt;/span&gt; {
  &lt;span class=&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;transformation&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;/span&gt;) {
    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; chainable = &lt;span class=&quot;hljs-title class_&quot;&gt;Matrix&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;identity&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;4&lt;/span&gt;)
    chainable.#operationStack = []
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; chainable
  }

  #operationStack?: &lt;span class=&quot;hljs-title class_&quot;&gt;Matrix&lt;/span&gt;[]

  &lt;span class=&quot;hljs-keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;values&lt;/span&gt;() {
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (&lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.#operationStack) {
      &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; operationStack = &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.#operationStack
      &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.#operationStack = &lt;span class=&quot;hljs-literal&quot;&gt;undefined&lt;/span&gt;
      &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; result = operationStack.&lt;span class=&quot;hljs-title function_&quot;&gt;reduceRight&lt;/span&gt;(
        &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;result, operation&lt;/span&gt;) =&amp;gt;&lt;/span&gt; result.&lt;span class=&quot;hljs-title function_&quot;&gt;mul&lt;/span&gt;(operation),
        &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;
      )
      &lt;span class=&quot;hljs-variable language_&quot;&gt;super&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;values&lt;/span&gt; = result.&lt;span class=&quot;hljs-property&quot;&gt;values&lt;/span&gt;
    }
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-variable language_&quot;&gt;super&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;values&lt;/span&gt;
  }

  #&lt;span class=&quot;hljs-title function_&quot;&gt;pushOperation&lt;/span&gt;(&lt;span class=&quot;hljs-attr&quot;&gt;operation&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;Matrix&lt;/span&gt;): &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt; {
    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!&lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.#operationStack) {
      &lt;span class=&quot;hljs-keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Error&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Attempted to push operation to non-chainable matrix&amp;#x27;&lt;/span&gt;)
    }
    &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;.#operationStack.&lt;span class=&quot;hljs-title function_&quot;&gt;push&lt;/span&gt;(operation)
    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;demo&quot; tabindex=&quot;-1&quot;&gt;The Demo: &lt;code&gt;&amp;lt;pixel-clock&amp;gt;&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;
  The end-of-chapter exercise for chapter 5 is to use your matrix and canvas implementation
  to color in a pixel for every hour of a 12 hour analog clock. I did two things I really
  didn’t have to for this exercise: add animated “hands” and perform the rendering in a &lt;a
    href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API&quot;
  &gt;Web Worker&lt;/a&gt;.
&lt;/p&gt;
&lt;div&gt;
  &lt;pixel-clock-demo&gt;
    &lt;noscript&gt;
      &lt;p&gt;&lt;em&gt;View this dynamic content on the website with JavaScript enabled.&lt;/em&gt;&lt;/p&gt;
    &lt;/noscript&gt;
    &lt;script&gt;
    // Ensure that syndicated copies of this content don’t show a &quot;Loading…&quot; message. This is
  // necessary because some feed readers try to execute JavaScript.
  const firstPartyHosts = [&#39;limulus.net&#39;, &#39;localhost&#39;, &#39;127.0.0.1&#39;]
  if (firstPartyHosts.some((host) =&gt; window.location.host.includes(host))) {
    document.write(&#39;&lt;p&gt;&lt;em&gt;Loading…&lt;/em&gt;&lt;/p&gt;&#39;)
  } else {
    document.write(&#39;&lt;p&gt;&lt;em&gt;View this dynamic content on the website with JavaScript enabled.&lt;/em&gt;&lt;/p&gt;&#39;)
  }
    &lt;/script&gt;
  &lt;/pixel-clock-demo&gt;
&lt;/div&gt;
&lt;p&gt;
  Now that I have some hands-on experience with Web Workers I expect to be able to offload
  the work of the ray tracer off the main thread, and possibly even parallelize the work
  into multiple workers.
&lt;/p&gt;
&lt;h2 id=&quot;up-next%3A-finally-casting-some-rays!&quot; tabindex=&quot;-1&quot;&gt;
  Up Next: Finally Casting Some Rays!
&lt;/h2&gt;
&lt;p&gt;
  Now that these fundamentals are out of the way and I’ve got this site hosted where I want
  it, there should be less of a delay until the next post. With any luck the next post will
  also not be quite as long!
&lt;/p&gt;
</description>
      <pubDate>Sat, 09 Dec 2023 01:30:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/penumbra/journal/canvas-and-matrices/</guid>
    </item>
    <item>
      <title>Chapter 1: Tuples</title>
      <link>https://limulus.net/penumbra/journal/tuples/</link>
      <description>&lt;p&gt;
  The first chapter of the book focuses on setting up a foundational “tuple” library for
  operations on vectors and points. I didn’t really expect there to be much to show for this
  other than working tests. But the chapter ended with a suggestion for creating a small
  program to experiment with a mini physics simulation that fires projectiles at various
  angles and velocities and have their trajectories effected by gravity and wind.
&lt;/p&gt;
&lt;p&gt;
  I decided to take this a step further and create a web component that would animate the
  projectile on a canvas. Here it is in action:
&lt;/p&gt;
&lt;div&gt;
  &lt;projectile-cannon-demo&gt;
    &lt;noscript&gt;
      &lt;p&gt;&lt;em&gt;View this dynamic content on the website with JavaScript enabled.&lt;/em&gt;&lt;/p&gt;
    &lt;/noscript&gt;
    &lt;script&gt;
    // Ensure that syndicated copies of this content don’t show a &quot;Loading…&quot; message. This is
  // necessary because some feed readers try to execute JavaScript.
  const firstPartyHosts = [&#39;limulus.net&#39;, &#39;localhost&#39;, &#39;127.0.0.1&#39;]
  if (firstPartyHosts.some((host) =&gt; window.location.host.includes(host))) {
    document.write(&#39;&lt;p&gt;&lt;em&gt;Loading…&lt;/em&gt;&lt;/p&gt;&#39;)
  } else {
    document.write(&#39;&lt;p&gt;&lt;em&gt;View this dynamic content on the website with JavaScript enabled.&lt;/em&gt;&lt;/p&gt;&#39;)
  }
    &lt;/script&gt;
  &lt;/projectile-cannon-demo&gt;
&lt;/div&gt;
&lt;p&gt;
  I attempted to represent the &lt;math display=&quot;inline&quot;&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;/math&gt; axis by
  increasing/decreasing the size of the projectile. Something seems off with that though.
&lt;/p&gt;
&lt;p&gt;
  GitHub Copilot was helpful when writing the &lt;a
    href=&quot;https://github.com/limulus/penumbra/blob/420867ab2c91f8bf42a4fa0e49cb4e927de9dbeb/src/lib/Tuple.spec.ts&quot;
  &gt;tuple tests&lt;/a&gt;. I could take the Gherkin test from the book, put it in a comment, and
  Copilot would generate the test code using the
  &lt;a
    href=&quot;https://github.com/limulus/penumbra/blob/420867ab2c91f8bf42a4fa0e49cb4e927de9dbeb/src/gherkin.ts&quot;
  &gt;Gherkin-inspired test suite functions&lt;/a&gt; I wrote.
&lt;/p&gt;
&lt;h2 id=&quot;looking-ahead&quot; tabindex=&quot;-1&quot;&gt;Looking Ahead&lt;/h2&gt;
&lt;p&gt;
  One thing that pained me as I wrote the implementation of the &lt;a
    href=&quot;https://github.com/limulus/penumbra/blob/420867ab2c91f8bf42a4fa0e49cb4e927de9dbeb/src/lib/Tuple.ts&quot;
  &gt;Tuple methods&lt;/a&gt; was knowing how inefficient they will be running in a JavaScript
  runtime. Vector math is the usual use case for [SIMD] instructions, but presumably
  JavaScript engines are not detecting that these operations could be compiled to SIMD
  instructions. I did a bit of forward research though and discovered that &lt;a
    href=&quot;https://webassembly.org/&quot;
  &gt;WebAssembly&lt;/a&gt; has SIMD support! At some point I plan to look at reimplementing Penumbra
  in &lt;a href=&quot;https://www.assemblyscript.org/&quot;&gt;AssemblyScript&lt;/a&gt; — but I first want a plain
  JavaScript baseline to compare against.
&lt;/p&gt;
&lt;p&gt;Onward to chapter 2!&lt;/p&gt;
</description>
      <pubDate>Sun, 19 Nov 2023 02:51:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/penumbra/journal/tuples/</guid>
    </item>
    <item>
      <title>One More Yak to Shave</title>
      <link>https://limulus.net/penumbra/journal/one-more-yak/</link>
      <description>&lt;p&gt;
  The site is getting published. The CSS needs work. Now it’s time to try and get an
  in-browser test runner working. For this I have two goals:
&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;I should be able to take the Gherkin tests from the book and easily use them&lt;/li&gt;
  &lt;li&gt;The tests should be a part of this site&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  Initially I was thinking that I would use the Gherkin tests directly from the book — by
  using some existing tool to parse them and provide hooks for wiring up the steps. But I’m
  not really finding anything like that out there. I found &lt;a
    href=&quot;https://github.com/hyperjump-io/json-schema/blob/0d9b0fec778dfd46e4136987f03b9fcf0147ea39/lib/mocha-gherkin.spec.ts&quot;
  &gt;this approach&lt;/a&gt;
  which defines functions for each of the Gherkin prefixes, maps them to Mocha functions,
  which seems like a better approach.
&lt;/p&gt;
&lt;p&gt;
  &lt;a href=&quot;https://mochajs.org/&quot;&gt;Mocha&lt;/a&gt; also seems like a good choice for the test
  runner. And after a decent amount of trial and error I got it working how I want. I even
  got Eleventy’s dev server to reload the page when the code changes, which will be nice for
  development.
&lt;/p&gt;
&lt;p&gt;Remaining yak shaving tasks:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Set up an RSS feed for this blog&lt;/li&gt;
  &lt;li&gt;Redo site styles&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  But at this point I would rather get started on the first test. This stuff has taken up
  way too much time already.
&lt;/p&gt;
</description>
      <pubDate>Sat, 11 Nov 2023 22:20:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/penumbra/journal/one-more-yak/</guid>
    </item>
    <item>
      <title>Getting Nerd Sniped</title>
      <link>https://limulus.net/penumbra/journal/nerd-sniped/</link>
      <description>&lt;p&gt;
  The way &lt;a href=&quot;https://pragprog.com/titles/jbtracer/the-ray-tracer-challenge/&quot;
  &gt;The Ray Tracer Challenge&lt;/a&gt; starts is refreshing. The “Getting Started” section doesn’t
  spend time on any development environment setup. Instead it is a simple introduction to
  the &lt;a href=&quot;https://cucumber.io/docs/gherkin/&quot;&gt;Gherkin&lt;/a&gt; syntax then a notes on some
  typical pitfalls. Then the first chapter gives a brief introduction to points and vectors
  and throws its first test at you. It doesn’t even remind you that you need to choose a
  language.
&lt;/p&gt;
&lt;p&gt;
  I thought about what I wanted to use for my implementation. I could have chosen this as a
  way to introduce myself to a new language, but I decided to initially go with something I
  was familiar with: TypeScript compiled to JavaScript running in the browser. This way I
  could focus on the ray tracer itself and demos would be easy to share. After some research
  I also concluded that there is runway for performance enhancements like WebAssembly, WebGL
  and WebGPU.
&lt;/p&gt;
&lt;p&gt;
  Unfortunately this raises lots of questions that all have to get addressed before I even
  get started!
&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;What would I name the project?&lt;/li&gt;
  &lt;li&gt;Where will I publish the site?&lt;/li&gt;
  &lt;li&gt;What will I use to build the site?&lt;/li&gt;
  &lt;li&gt;
    What test runner will I use? (My usual choice, Jest is great for a JSDOM environment,
    but that won’t work for advanced web features.)
  &lt;/li&gt;
  &lt;li&gt;How do I hook up the Gherkin tests from the book to my test runner?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  For the name after some poking around on Wikipedia I settled on “Penumbra”. There’s
  probably some better names I could have chosen, but I couldn’t find any other ray tracers
  with that name already.
&lt;/p&gt;
&lt;p&gt;
  Deciding on where to publish the site was pretty straightforward. My &lt;a
    href=&quot;https://limulus.net/&quot;
  &gt;personal site&lt;/a&gt; would make sense, but it’s all tied up with its own repository. I also
  don’t have any static site generator for it. My &lt;a href=&quot;https://unallocated.com/&quot;
  &gt;professional site&lt;/a&gt; does have a static site generator (&lt;a href=&quot;https://gohugo.io/&quot;
  &gt;Hugo&lt;/a&gt;) but I couldn’t convince myself it was an appropriate place for this project. So
  I decided to create a new repository for the project and use &lt;a
    href=&quot;https://pages.github.com/&quot;
  &gt;GitHub Pages&lt;/a&gt; to publish it.
&lt;/p&gt;
&lt;p&gt;
  My past experience with Hugo was alright, but Go template syntax kinda irks me. So I
  decided to give &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; a try, seeing as how it is the
  default for GitHub Pages. This was the first real &lt;a href=&quot;https://xkcd.com/356/&quot;
  &gt;nerd snipe&lt;/a&gt;.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a
      href=&quot;https://limulus.net/penumbra/journal/nerd-sniped/#fn1&quot;
      id=&quot;fnref1&quot;
    &gt;[1]&lt;/a&gt;&lt;/sup&gt; Jekyll is written in Ruby, and I figured I could just install the gem in
  my project. Unfortunately by default Ruby’s &lt;code&gt;bundle&lt;/code&gt; tool does not by default
  install gems in the project directory like I expect a package manager to work in this
  decade. It’s possible to get it to work like this, but after frustration with current
  documentation on how to do it not matching the version of Ruby that macOS ships with I
  decided to try to do my development in a devcontainer. I got that working but wasn’t
  really happy with having to run Docker locally just for this project.
&lt;/p&gt;
&lt;p&gt;
  After going back to the drawing board I started researching other static site generators.
  That’s when I came across &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt;, which is a fast
  Node.js based site generator. I actually had known about it for a number of months, but
  had completely forgotten about it. So now that is what I am setting up and is what this
  site is using.
&lt;/p&gt;
&lt;p&gt;
  Now of course I am getting nerd sniped trying to figure out how to use Eleventy and
  general web design things:
&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    Why are &lt;a href=&quot;https://www.11ty.dev/docs/languages/webc/&quot;&gt;WebC&lt;/a&gt; templates not
    working like I expect?
  &lt;/li&gt;
  &lt;li&gt;How do I get a list of blog posts to show up on the home page?&lt;/li&gt;
  &lt;li&gt;How do I get footnotes in a Markdown file to get rendered?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  I’ve managed to work through these now, but I’ve run into issues with Eleventy’s dev
  server not updating when it should and mysterious issues with the &lt;code&gt;webc:keep&lt;/code&gt;
  attribute in bundling mode. This has me wondering if my plan for using Eleventy’s dev
  server for development will work out. But that will have to be the next entry…
&lt;/p&gt;
&lt;section class=&quot;footnotes section-separator&quot;&gt;
  &lt;ol class=&quot;footnotes-list&quot;&gt;
    &lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;
      &lt;p&gt;
        Because I have a an overly complicated project creation utility for personal
        TypeScript projects I always wind up starting a project by refreshing the
        dependencies for that utility. This time that led me to discovering that &lt;a
          href=&quot;https://www.nodegit.org&quot;
        &gt;node-git&lt;/a&gt; has stagnated and is no longer providing up-to-date pre-compiled
        binaries for the latest versions of Node. This results in 3-6 minutes of compile
        time when installing node-git. Yikes! That’s a lot for a CLI utility that is
        supposed to be run via &lt;code&gt;npm create&lt;/code&gt;. So I decided to spend the time to
        switch it over to &lt;a href=&quot;https://github.com/steveukx/git-js&quot;&gt;simple-git&lt;/a&gt; which
        is pure JavaScript and doesn’t require any compilation. Thankfully I had written
        tests for the git functionality which did not mock out &lt;a
          href=&quot;https://www.nodegit.org&quot;
        &gt;node-git&lt;/a&gt; and so swapping out the library was straightforward. &lt;a
          href=&quot;https://limulus.net/penumbra/journal/nerd-sniped/#fnref1&quot;
          class=&quot;footnote-backref&quot;
        &gt;↩︎&lt;/a&gt;
      &lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/section&gt;
</description>
      <pubDate>Tue, 31 Oct 2023 22:52:00 GMT</pubDate>
      <dc:creator>Eric McCarthy</dc:creator>
      <guid>https://limulus.net/penumbra/journal/nerd-sniped/</guid>
    </item>
  </channel>
</rss>