<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <link href="https://kofi.sexy/feed" rel="self" type="application/atom+xml" />
  <link href="https://kofi.sexy/" rel="alternate" type="text/html" />
  <title type="html">Kofi Gumbs</title>
  <author>
    <name>Kofi Gumbs</name>
  </author>

  
  
    <entry>
      
      <link href="https://kofi.sexy/blog/gameboy-game" rel="alternate" type="text/html" title="Making a Game Boy game in 2025" />
      <published>2025-10-16T00:00:00+00:00</published>
      <updated>2025-10-16T00:00:00+00:00</updated>
      <id>https://kofi.sexy/blog/gameboy-game</id>
      <title type="html">Making a Game Boy game in 2025</title>
      <author>
        <name>Kofi Gumbs</name>
      </author>

      
        <category term="blog" />
      

      
        <content type="html" xml:base="https://kofi.sexy/blog/gameboy-game"><![CDATA[<p>I recently developed a custom Game Boy game, flashed it onto physical cartridges, and sold them at <a href="https://www.instagram.com/p/DOhHMcHjRMa/?img_index=1">a local makers festival</a>.
The whole process was surprisingly straight-forward, and I think more professional developers should give it a try.
As a career-long software guy, it felt rewarding to exchange real money for a tangible good.</p>

<video autoplay="" muted="" controls="" loop="" src="/gameboy-game/hot-dog-race.mov" alt="Demo of the Hot Dog Race Game Boy game, in which the player selects a character (Ketchup, Mustard, or Relish) and then runs around a baseball field. The player can shoot opponent racers to slow them down.">
</video>

<p>My game, Hot Dog Race, is based on the 4th-inning show at Baltimore Oriole’s home games.
Many baseball stadiums have something similar: the crowd cheers for their favorite character as they do a quick and silly sideline race.
The Game Boy game is just as simple.
The player picks a condiment, and then runs around the bases trying to be the first hot dog to reach home plate.</p>

<p>I developed the game in <a href="https://www.gbstudio.dev">GB Studio</a> which is the simplest option for a first time game designer.
In the future, it’d be fun to try the C-based <a href="https://github.com/gbdk-2020/gbdk-2020">GBDK</a>, but I ignored my inner tinkerer here for the sake of finishing the project.
The built-in GB Studio engines are solid and include the batteries needed to make a professional game.
All in all, I spent more time on the art design than I did the game logic, which is a testament to how comprehensive GB Studio is.</p>

<p>I used <a href="http://piskelapp.com">Piskel</a> to hand-draw all of my sprites.
Sometimes I could start with a scaled down PNG, like on the character select screen which uses designs by my friend at <a href="http://makemore410.com">Makemore</a>.
However, I ended up needing to manually paint pixels in every graphic at some point.
The game’s music is from <a href="https://tiptoptomcat.itch.io/8-bit-gameboy-songs-dx-gb-studio">TipTopTomCat’s 8-bit collection</a>.</p>

<p>Getting the game onto physical media was also pretty easy.
I used <a href="https://handheldlegend.com/products/game-boy-flash-cartridge-512kb-rom-ferrante-crafts">512KB Ferrante Crafts cartridges</a>, which turned out to be larger than I needed.
Apparently, it’s cheapest to buy game reproductions (like Pokemon Crystal) from AliExpress and overwrite them, but this felt another time to ignore the tinkerer.
GB Studio exports <code>.gbc</code> ROMs, which I loaded onto the empty cartridges using a <a href="https://www.gbxcart.com">GBxCart RW</a>, which works with the open-source <a href="https://github.com/lesserkuma/FlashGBX">FlashGBX</a> program.</p>

<p><img src="/gameboy-game/hot-dog-race.jpg" alt="Hot Dog Race cartridge beside a Game Boy color" /></p>

<p>You can <a href="/gameboy">play Hot Dog Race online</a> and even buy a cartridge while supply lasts!
My GB Studio project is <a href="https://github.com/kofigumbs/hot-dog-race">available on GitHub</a> should you want to give Game Boy dev a try yourself.</p>]]></content>
      
    </entry>
  
    <entry>
      
      <link href="https://kofi.sexy/blog/google-calendar-ics-sync" rel="alternate" type="text/html" title="Working around Google Calendar sync delays" />
      <published>2024-07-08T00:00:00+00:00</published>
      <updated>2024-07-08T00:00:00+00:00</updated>
      <id>https://kofi.sexy/blog/google-calendar-ics-sync</id>
      <title type="html">Working around Google Calendar sync delays</title>
      <author>
        <name>Kofi Gumbs</name>
      </author>

      
        <category term="blog" />
      

      
        <content type="html" xml:base="https://kofi.sexy/blog/google-calendar-ics-sync"><![CDATA[<p>Google seems to intentionally handicap it’s “subscribe from URL” feature by limiting how often it resyncs.
In an ideal world, users could provide an ICalendar (ICS) URL and easily select how often they want to check for updates.
Most calendar clients work this way already.
Google Calendar is a lone holdout here though—users cannot set their own intervals, and <a href="https://support.google.com/calendar/thread/137193917?hl=en&amp;msgid=137207461">syncs can take days</a>.</p>

<p>As an individual user, this behavior is annoying.
But as a calendar app developer, this behavior presents a real challenge.
Given the prevalence of Google Calendar, calendar-publishing apps need to account for these delays.
To solve the issue in my app, I built <a href="https://opencal.dev">OpenCal</a>, an ICS-to-Google-Calendar-API sync service.</p>

<p>It feels like a concession to build a service dedicated to working around an intentional limitation.
I suspect Google’s behavior here is an attempt to keep users within their ecosystem.
Like the “blue bubble” iMessage effect, it’s a subtle, pervasive reminder that you’re not on the blessed path.
I initially tried documenting this behavior in my app and explaining how to work around it manually (i.e. appending arbitrary params to the URL).
This proved to be just too much friction though.</p>

<p>OpenCal is a <a href="https://opencal.dev/docs">minimal API</a> that gets you a fast user experience without having to integrate to Google Calendar yourself.
There’s a generous free tier that’s more reliable than “subscribe from URL” and nicer to use than Google Calendar’s V3 API.
I originally extracted OpenCal from a user-facing calendar app, and I’ve been using it myself for months without issue.
Hopefully it saves you or a developer you know a bit of exasperation, even if it does feel like a concession.</p>]]></content>
      
    </entry>
  
    <entry>
      
      <link href="https://kofi.sexy/blog/zero-downtime-render-disk" rel="alternate" type="text/html" title="Zero-downtime deploys for Render apps with disk storage" />
      <published>2024-02-26T00:00:00+00:00</published>
      <updated>2024-02-26T00:00:00+00:00</updated>
      <id>https://kofi.sexy/blog/zero-downtime-render-disk</id>
      <title type="html">Zero-downtime deploys for Render apps with disk storage</title>
      <author>
        <name>Kofi Gumbs</name>
      </author>

      
        <category term="blog" />
      

      
        <content type="html" xml:base="https://kofi.sexy/blog/zero-downtime-render-disk"><![CDATA[<p>I feel obligated to write this post given how difficult it was to find prior art online.
At one point in the journey, my Google search yielded only a single (albeit helpful) Mastadon toot.
Hopefully, the content here assists the next wandering soul who ventures down this seemingly reasonable path.</p>

<p><strong>Here’s the summary for those just here for the solution:</strong> you can deploy a web server, like Caddy, as a separate Web Service in front of your app.
Caddy <a href="https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#lb_try_duration">can be configured</a> to “hold” incoming requests until it can connect to the downstream app, providing a window for Render to perform the instance swap.
Caddy’s default docker image uses <code>setcap cap_net_bind_service</code> though, which must be unset before Render can run the image.</p>

<h2 id="cloud-disk-storage-is-almost-perfect">Cloud disk storage is almost perfect</h2>

<p>Render, like many PaaS providers today, offers persistent disk storage for their cloud VMs.
This is a significant development since the Heroku days, where any persistence required dedicated services like Postgres, Redis, or S3.
Now, apps with low to moderate traffic can simplify their architecture by storing data on files directly on the app server (glossing over the magic of cloud volume mounting).</p>

<p>Unfortunately, enabling persistent disks comes with some downsides.
Here’s how <a href="https://docs.render.com/disks#disk-limitations-and-considerations">Render describes the limitations</a>, which is consistent with other PaaS like Fly.io and Railway:</p>

<blockquote>
  <p>Adding a disk to a service prevents zero-downtime deploys. This is because:</p>
  <ul>
    <li>When you redeploy your service, Render stops the existing instance before bringing up the new instance.</li>
    <li>This instance swap takes a few seconds, during which your service is unavailable.</li>
    <li>This is a necessary safeguard to prevent data corruption that can occur when different versions of an app read and write to the same disk simultaneously.</li>
  </ul>
</blockquote>

<p>Render has reasonable default behavior here, but the rationale in the last bullet point does not apply to all apps.
My app uses SQLite as its datastore, so as long as I only interact with the disk using concurrency-safe SQLite APIs, there is no risk of data corruption.
Ideally, Render would allow me to opt out of this safeguard, since my technology stack already handles the issue.</p>

<h2 id="all-problems-in-computer-science-can-be-solved-by-another-level-of-indirection---david-wheeler">“All problems in computer science can be solved by another level of indirection” <a href="https://en.wikipedia.org/wiki/David_Wheeler_%28computer_scientist%29#Quotes">- David Wheeler</a></h2>

<p>Stepping back a bit, there’s no fundamental reason the instance swap needs to result in system downtime.
Render actually solves this problem for its free tier apps, which go to sleep if they haven’t received traffic for some time.
Whenever the app is next pinged, Render will wait for it to start back up before routing the request.
This means that end users experience delays instead of interruptions.</p>

<p>We can model that same behavior by deploying a separate web server in front of our app.
If the web server is ever unable to open a connection to the downstream app, then it will simply wait a bit and try again.
Configuring Caddy with <code>lb_try_duration 60s</code> tells it to retry the downstream app for up to 60 seconds before giving up with a 502 response.</p>

<p>At this point, the puzzle is solved in theory, but <a href="https://community.render.com/t/deploying-caddy-to-render/10437">trying to run Caddy on Render</a> errors with a cryptic message: <code>exec /usr/bin/caddy: operation not permitted</code>.
According to Google, this exact error message has only been mentioned once online (hopefully twice now) <a href="https://hdev.im/@eisenhorn/110387793844876245">by @eisenhorn@hdev.im</a> who nails the root cause:</p>

<blockquote>
  <p>Caddy’s container wants NET_BIND_SERVICE. Always… Apparently it comes from the official Dockerfile.</p>
</blockquote>

<p>And so, here’s the final Dockerfile that removes the unneeded capability and enables zero-downtime deploys to my downstream app:</p>

<pre><code class="language-dockerfile">FROM caddy
RUN setcap -r /usr/bin/caddy
COPY &lt;&lt;EOF /etc/caddy/Caddyfile
:10000 {
  reverse_proxy http://my-downstream-app:10000 {
    lb_try_duration 60s
  }
}
EOF
</code></pre>

<p>As a nice bonus, I can now deploy my downstream app as a Private Service since it only receives traffic from Caddy, within my Render private network.</p>]]></content>
      
    </entry>
  
    <entry>
      
      <link href="https://kofi.sexy/blog/multi-3" rel="alternate" type="text/html" title="Multi 3.0: Codesigning, dynamic libraries, and macOS notifications" />
      <published>2023-07-24T00:00:00+00:00</published>
      <updated>2023-07-24T00:00:00+00:00</updated>
      <id>https://kofi.sexy/blog/multi-3</id>
      <title type="html">Multi 3.0: Codesigning, dynamic libraries, and macOS notifications</title>
      <author>
        <name>Kofi Gumbs</name>
      </author>

      
        <category term="blog" />
      

      
        <content type="html" xml:base="https://kofi.sexy/blog/multi-3"><![CDATA[<p><a href="https://github.com/kofigumbs/multi">Multi</a> is a macOS app for creating native wrappers around your favorite websites.
Since Multi apps use Apple’s WebKit engine, they are generally less resource-intensive than their Electron equivalents.
The Multi runtime also connects web platform APIs to macOS native widgets, like notifications—the little feature that turned the 3.0 release into a complete rewrite.</p>

<p>Prior to this release, Multi used the deprecated <code>NSUserNotification</code> to show banner alerts.
These APIs still seem to work OK, but fixing some of the long-standing GitHub issues would have required me to invest further into a sinking ship.
I figured migrating to the newer <code>UNNotification</code> might magically solve some of those issues, but even if it didn’t I’d still be in a better position for researching solutions.
At a surface level the APIs work similarly, and it was simple to get the new code compiling;
but when I went to test my Multi app, no notifications appeared.</p>

<p>Eventually I discovered that <code>UNUserNotificationCenter</code> automatically denies notification permission requests unless the app is codesigned.
This restriction was a problem for Multi because <code>codesign</code> requires that an apps bundle contains no references to external artifacts.
In the old architecture, creating a Multi app would add a symlink in the place where macOS expected the main executable.
That symlink pointed to an executable runtime within Multi itself, which meant that updating Multi automatically updates any wrapper apps.
<code>codesign</code> complains (rightfully so) with this setup since the executable artifact lives outside of the signed bundle.</p>

<p>The solution: I now ship the runtime as a dynamic library instead of an executable, and Multi wrapper apps contain a small initializer binary that loads it.
<code>codesign</code> is happy because the resulting bundle is statically self-contained.
And users are happy because updating Multi still updates behavior in existing wrapper apps.
The initializer binary itself should only need to change if I’ve done something tragically wrong in <a href="https://github.com/kofigumbs/multi/blob/main/Sources/MultiStub/main.swift">these 10 lines of Swift</a>.</p>]]></content>
      
    </entry>
  
    <entry>
      
      <link href="https://kofi.sexy/blog/modern-spas" rel="alternate" type="text/html" title="Modern SPAs without bundlers, CDNs, or NodeJS" />
      <published>2023-02-13T00:00:00+00:00</published>
      <updated>2023-02-13T00:00:00+00:00</updated>
      <id>https://kofi.sexy/blog/modern-spas</id>
      <title type="html">Modern SPAs without bundlers, CDNs, or NodeJS</title>
      <author>
        <name>Kofi Gumbs</name>
      </author>

      
        <category term="blog" />
      

      
        <content type="html" xml:base="https://kofi.sexy/blog/modern-spas"><![CDATA[<p>I typically start new front-end prototypes with a single HTML file that I view using a <code>file://</code> URL.
I enjoy this practice of incrementally growing my projects, so I’ll keep working in that single file for as long as I can.
Once it becomes unwieldy, I’ll split out the CSS and JavaScript into dedicated files.
Then when manual DOM manipulation gets too complex, I’ll reach for a front-end framework.
Until recently though, I wasn’t sure how to make this step feel incremental.
Most framework installations recommend <code>npm install</code>, which means my project will now depend on NodeJS.
Some frameworks have a hosted CDN option, but I’m similarly uncomfortable accepting that infrastructural dependency.
Ideally, I’d just grab the framework files, import them from my JavaScript, and then carry on with my <code>file://</code> URL.</p>

<p>Well, creating that ideal setup was easier than I expected.
My first key discovery was the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap"><code>&lt;script type=importmap&gt;</code></a> element.
Import maps let you write JavaScript modules that depend on named packages without using a bundler.
The end result is a SPA that feels modern and standard, but without the <a href="https://www.solidjs.com/tutorial/introduction_basics">need for a compilation step</a>:</p>

<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;script type=importmap&gt;
  {
    "imports": {
      "solid-js": "/node_modules/solid-js/dist/solid.js",
      "solid-js/html": "/node_modules/solid-js/html/dist/html.js",
      "solid-js/web": "/node_modules/solid-js/web/dist/web.js"
    }
  }
&lt;/script&gt;
&lt;script type=module&gt;
  // standard import declarations thanks to the import map above
  import html from 'solid-js/html'
  import { render } from 'solid-js/web'

  const HelloWorld = () =&gt; {
    // tagged template literals feel close enough to JSX (the defacto standard)
    return html`&lt;div&gt;Hello World!&lt;/div&gt;`
  }

  render(HelloWorld, document.getElementById('app'))
&lt;/script&gt;
&lt;main id=app&gt;&lt;/main&gt;
</code></pre>

<p>That’s almost all of it.
This HTML file can be interpreted by most modern browsers, but it references files in <code>/node_modules/solid-js</code> which must be installed.
I <em>could</em> do this with <code>npm install</code>, but it’s surprisingly straightforward to download the package directly:</p>

<pre><code class="language-bash">download-package() {
  set -eo pipefail
  local PACKAGE_NAME=$1
  local PACKAGE_VERSION=$2
  local PACKAGE_CHECKSUM=$3
  local PACKAGE_FILE="$PACKAGE_NAME-$PACKAGE_VERSION.tgz"

  # download the tarball from NPM's registry
  mkdir -p "node_modules/$PACKAGE_NAME"
  curl "https://registry.npmjs.org/$PACKAGE_NAME/-/$PACKAGE_FILE" &gt; "node_modules/$PACKAGE_FILE"

  # verify the tarball's SHA512 checksum
  local PACKAGE_SHA=`shasum -b -a 512 "node_modules/$PACKAGE_FILE" | awk '{ print $1 }' | xxd -r -p | base64`
  [ "$PACKAGE_SHA" == "$PACKAGE_CHECKSUM" ]

  # uncompress the package into a directory matching its name
  gunzip -dc "node_modules/$PACKAGE_FILE" | tar -xf - --strip-components 1 -C "node_modules/$PACKAGE_NAME"
}

# install by manually providing metadata from https://registry.npmjs.org/solid-js
download-package solid-js 1.6.6 "5x33mEbPI8QLuywvFjQP4krjWDr8xiYFgZx9KCBH7b0ZzypQCHaUubob7bK6i+1u6nhaAqhWtvXS587Kb8DShA=="
</code></pre>

<p>That’s all of it: my bundler-free setup for writing modern JavaScript apps.
When I need to add a dependency, I invoke <code>download-package</code> and then declare it in the import map.
I like this setup because it feels like I’m only using the bits of the NodeJS ecosystem that I need right now.
Later in the project, I could opt into more of what NodeJS has to offer, but it’s neat that I’m not forced into it just to use a UI framework.</p>]]></content>
      
    </entry>
  
    <entry>
      
      <link href="https://kofi.sexy/blog/typebeat-v010" rel="alternate" type="text/html" title="Typebeat, checkpoint 0.1.0" />
      <published>2022-01-31T00:00:00+00:00</published>
      <updated>2022-01-31T00:00:00+00:00</updated>
      <id>https://kofi.sexy/blog/typebeat-v010</id>
      <title type="html">Typebeat, checkpoint 0.1.0</title>
      <author>
        <name>Kofi Gumbs</name>
      </author>

      
        <category term="blog" />
      

      
        <content type="html" xml:base="https://kofi.sexy/blog/typebeat-v010"><![CDATA[<p>I occasionally <a href="/music/tide">make music</a>.
And like many a programmer-turned-music-maker, my musical process often devolves into writing code to optimize various parts of my workflow.
Typebeat is my latest contribution to that tradition.
It’s a virtual <a href="https://en.wikipedia.org/wiki/Groovebox">groovebox</a> that’s entirely keyboard-operated:</p>

<iframe src="https://www.youtube-nocookie.com/embed/RT0qUB4gbas" title="A screen recording demonstrating how I create music with Typebeat. The app is laid out like a QWERTY keyboard with labels for each key. When I press a key on my keyboard (shown in a video overlay) the corresponding key in the app is illuminated and its effect is triggered." allow="fullscreen"></iframe>

<blockquote>
  <p><strong>Edit (2022-03-19):</strong> I decided to make the source code public as well. Enjoy! <a href="https://github.com/kofigumbs/typebeat">github.com/kofigumbs/typebeat</a></p>
</blockquote>

<p>You can play with a <em>pre-alpha</em> demo at <a href="https://typebeat.kofi.sexy">typebeat.kofi.sexy</a>.
I think it would be fun to release a more substantial version someday, but that version would look much different than today’s.
Before making that detour, I wanted to hit pause and reflect on how the project came to this point.</p>

<p>Typebeat draws inspiration from two computing environments that make me feel productive and creative.
The first is <a href="https://livecode.nyc">live-coding</a>, the performance art of writing code to generate media.
I love watching live-coding shows because the performers typically insist on showing their work.
The code is as much a part of the performance as the resulting visuals and audio.
Live-coders use their input device as their instrument by creating programs that the computer then interprets.
I think of Typebeat as starting with that idea, keyboard as instrument, and making the analogy more direct.
Press <code>N</code> to play a drum kick.
Press <code>K</code> to play a clap.
Type <code>NYKY</code> to play a “boots and cats” house beat.</p>

<p>Of course this sort of direct mapping doesn’t scale: there are more musical commands than there are keys on a keyboard.
So Typebeat also takes inspiration from <a href="https://en.wikipedia.org/wiki/Vi">vi</a> and incorporates a modal interface.
In Audition Mode, Typebeat works as I mentioned above, with each key triggering a different sound.
But in Note Mode, Typebeat transposes the active sound, letting you access notes in a 2-octave range.
Send Mode lets you route your sound to different effect buses, and then Return Mode lets you configure what each of those effect buses actually do.
Like my experience with vi, Typebeat is incredibly fast to navigate once learned.</p>

<hr />

<p>I’m calling this post a checkpoint and not a release since it’s not ready for general use.
Focusing on the keyboard input device was a helpful design constraint, but I think I insisted on it too heavily.
Sometimes, particularly when I <em>don’t</em> know exactly what I want to hear, playing with knobs and sliders helps me to discover new ideas.
Nudging on-screen values with a keyboard does not produce that same effect for me.
Similarly, some ideas are just difficult to fit onto a screen if I insist that everything must be viewed as if it were literally a keyboard.
Moving forward, I’m planning to experiment more with analog input devices and make the workflow more focused.</p>]]></content>
      
    </entry>
  
    <entry>
      
      <link href="https://kofi.sexy/blog/multi-retrospective" rel="alternate" type="text/html" title="A retrospective on Multi" />
      <published>2021-08-26T00:00:00+00:00</published>
      <updated>2021-08-26T00:00:00+00:00</updated>
      <id>https://kofi.sexy/blog/multi-retrospective</id>
      <title type="html">A retrospective on Multi</title>
      <author>
        <name>Kofi Gumbs</name>
      </author>

      
        <category term="blog" />
      

      
        <content type="html" xml:base="https://kofi.sexy/blog/multi-retrospective"><![CDATA[<p>Perhaps my favorite phase of any project is when I get to call it done.
<a href="https://github.com/kofigumbs/multi">Multi</a> has come a long way since <a href="/blog/multi">its initial announcement</a>.
It was neat to have built a tool that (1) had become part of my daily workflow and (2) had so many interesting opportunities for extension.
Neither of those points are true for me today, so I decided to <a href="https://github.com/kofigumbs/multi/commit/14f2d1b5524a8477f203d8e1cb4b6100ea35a5f2">make Multi free</a> for new users.</p>

<h2 id="selling">Selling</h2>

<p>Multi earned around $1500.
In total, that’s a pretty impressive number to me: coming out to ~$100/month.
The catch is that only 1/3 of those earnings came from sales.
The rest came from an API extension I made to support another developer’s use case–contract work, essentially.
I don’t have issues with contract work generally, but I don’t have capacity for any right now.
Deducting that one-off, Multi maybe sells one or two licenses in a normal month, and that’s not enough to offset the support effort.
I always suspected Multi would sell a bit more if I put in any amount of effort towards marketing, but I never gathered the enthusiasm for that.
<a href="https://macwright.com/2021/07/24/hacking-is-the-opposite-of-marketing.html">Marketing is the opposite of hacking.</a></p>

<h2 id="not-proficient-and-not-excited">Not proficient and not excited</h2>

<p>For the most part, I’ve been able to resolve user issues as they come up.
There are a couple bugs though that I can’t figure out.
I’ve spent several hours looking into these issues, and ultimately those sessions leave me intimately aware of my unfamiliarity with macOS development.
Reflecting on those experiences, I realized that I need at least one of the following conditions in order to stay motivated:</p>

<ol>
  <li>I am in a professional setting: “I am being paid to solve this bug.”</li>
  <li>I am aiming for proficiency: “This bug is something I need to understand.”</li>
  <li>I am genuinely interested in the problem space: “What a fun bug!”</li>
</ol>

<p>The previous section explains how Multi doesn’t fall under motivation #1.
Since I no longer use Multi myself and am not aiming for proficiency in AppKit, Multi work has never fit into #2 and has gradually slipped out of #3.
Thinking through that list helped me realize that I’m ready to move on to other projects.</p>

<p><em>Edit: Migrating Multi to SwiftUI helped the project better align with motivations #2 and #3; hence <a href="/blog/multi-3">Multi 3.0</a>.</em></p>

<hr />

<p>Multi 2.1.4 is the first version that removes the license key checks.
I will continue supporting Multi 2.1.3 installations until their license keys expire.
I also plan to clean up the GitHub issues to make the project more amenable to outside contributions.
If Multi stirs any of your core motivations, I hope you <a href="https://github.com/kofigumbs/multi">stop by</a>!</p>]]></content>
      
    </entry>
  
    <entry>
      
      <link href="https://8thlight.com/blog/jerome-goodrich/thomas-countz/2021/08/17/collaborative-craft-stealing-fire-kofi-gumbs.html" rel="alternate" type="text/html" title="Ambitious Side Projects (Collaborative Craft podcast)" />
      <published>2021-08-17T10:00:00+00:00</published>
      <updated>2021-08-17T10:00:00+00:00</updated>
      <id>https://8thlight.com/blog/jerome-goodrich/thomas-countz/2021/08/17/collaborative-craft-stealing-fire-kofi-gumbs</id>
      <title type="html">Ambitious Side Projects (Collaborative Craft podcast)</title>
      <author>
        <name>Kofi Gumbs</name>
      </author>

      
        <category term="talk" />
      

      
    </entry>
  
    <entry>
      
      <link href="https://kofi.sexy/this-page-is-intentionally-left-black" rel="alternate" type="text/html" title="This page is intentionally left Black" />
      <published>2021-03-25T00:00:00+00:00</published>
      <updated>2021-03-25T00:00:00+00:00</updated>
      <id>https://kofi.sexy/this-page-is-intentionally-left-black</id>
      <title type="html">This page is intentionally left Black</title>
      <author>
        <name>Kofi Gumbs</name>
      </author>

      
        <category term="experiment" />
      

      
    </entry>
  
    <entry>
      
      <link href="https://kofi.sexy/talk/teaching-webgl-to-dance-to-music" rel="alternate" type="text/html" title="Teaching WebGL to dance to music" />
      <published>2020-11-06T00:00:00+00:00</published>
      <updated>2020-11-06T00:00:00+00:00</updated>
      <id>https://kofi.sexy/talk/teaching-webgl-to-dance-to-music</id>
      <title type="html">Teaching WebGL to dance to music</title>
      <author>
        <name>Kofi Gumbs</name>
      </author>

      
        <category term="talk" />
      

      
    </entry>
  
</feed>
