<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0">
  <channel>
    <title>nathanherald.com feed</title>
    <link>https://nathanherald.com/</link>
    <description>Recent posts from nathanherald.com</description>
    <lastBuildDate>Sat, 19 Sep 2026 20:43:46 +0200</lastBuildDate>

    <item>
      <title>Talib Kweli &amp; Hi Tek (Reflection Eternal) - The Blast</title>
      <link>https://nathanherald.com/posts/links/reflection-eternal-the-blast/</link>
      <pubDate>Sat, 19 Sep 2026 20:43:00 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/reflection-eternal-the-blast/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/ihItq4ZQ8LM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;</description>
    </item>

    <item>
      <title>TIL about Solid and FOAF</title>
      <link>https://nathanherald.com/posts/til-about-solid-and-foaf/</link>
      <pubDate>Fri, 26 Jun 2026 10:56:07 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/til-about-solid-and-foaf/</guid>
      <description>&lt;h1 id="til-about-solid-and-foaf"&gt;TIL about Solid and FOAF&lt;/h1&gt;
&lt;p&gt;I've had a note sitting in my reminders for a few days: &lt;em&gt;"write
something about that solid thing and linked data and foaf."&lt;/em&gt; So I
finally dug in. As I &lt;a
href="https://nathanherald.com/posts/practical-rdf-example/"&gt;wrote about
recently,&lt;/a&gt; I’m very interested in RDF-* and linked data and Solid and
FOAF both come up in my searches every time. So…&lt;/p&gt;
&lt;h2 id="solid"&gt;Solid&lt;/h2&gt;
&lt;p&gt;Solid (Social Linked Data) is Tim Berners-Lee's project to fix the
part of the web where your data lives inside someone else's box. Your
stuff goes in a &lt;em&gt;pod&lt;/em&gt; which is a little datastore you own and
“apps” get decoupled from the data. So instead of an app holding your
data hostage, it asks your pod for permission to read it. Switch apps,
keep everything. Flip a switch, revoke access.&lt;/p&gt;
&lt;p&gt;Which, yeah. Obviously we should own and control our own data. So
cool idea I guess.&lt;/p&gt;
&lt;h2 id="its-rdf-underneath"&gt;It's RDF underneath&lt;/h2&gt;
&lt;p&gt;The data in a pod is just RDF which is the same
subject/predicate/object triples from my &lt;a
href="https://nathanherald.com/posts/practical-rdf-example/"&gt;recent
post.&lt;/a&gt; Solid doesn't reinvent how data is shaped, it only seems
concerned about the container for the data.&lt;/p&gt;
&lt;p&gt;RDF and linked data are about &lt;em&gt;what your data means and how it
connects&lt;/em&gt;. Solid is about &lt;em&gt;where it lives and who gets to read
it&lt;/em&gt;. A pod is just somewhere to keep your slice of the big graph,
with a permission system bolted on.&lt;/p&gt;
&lt;p&gt;“Linked data” is the glue: triples whose subjects and objects are
URLs, and those URLs resolve to more triples. You follow from one fact
to the next across the web.&lt;/p&gt;
&lt;h2 id="foaf"&gt;FOAF&lt;/h2&gt;
&lt;p&gt;FOAF (Friend of a Friend) is the oldest real example of linked data
about &lt;em&gt;people&lt;/em&gt;. It’s a tiny RDF vocabulary:
&lt;code&gt;foaf:Person&lt;/code&gt;, &lt;code&gt;foaf:name&lt;/code&gt;,
&lt;code&gt;foaf:knows&lt;/code&gt;, &lt;code&gt;foaf:homepage&lt;/code&gt;. Dan Brickley and
Libby Miller started it around 2000. Probably the first
social-semantic-web thing.&lt;/p&gt;
&lt;p&gt;The vocabulary's been basically frozen for two decades and the
namespace still resolves. And it's what Solid leans on under the hood. A
Solid identity is a &lt;em&gt;WebID&lt;/em&gt;, and a WebID profile is more or less
a FOAF file describing you.&lt;/p&gt;
&lt;p&gt;So FOAF is the whole idea shrunk to its smallest form. No pods, no
server, no protocol. Just a file about you, linking to files about your
friends.&lt;/p&gt;
&lt;h2 id="a-real-solid-use-case"&gt;A real Solid use case&lt;/h2&gt;
&lt;p&gt;In Flanders, the public data company &lt;a
href="https://athumi.be/en/technologies/solid"&gt;Athumi&lt;/a&gt; put citizen
data on Solid pods. The first real use case was: you share your diploma
with a recruiter &lt;em&gt;on consent&lt;/em&gt;, for whatever scope and time window
you pick. Before, only the government could pull your diploma from the
official database. With a pod, you can hand it over yourself.&lt;/p&gt;
&lt;p&gt;And it’s still going. They’ve &lt;a
href="https://www.itsme-id.com/en-BE/business/blog/athumi-and-itsme-digital-student-certificate"&gt;extended
the same platform to digital student certificates&lt;/a&gt; through the itsme
app, with a student app called Dibbs as the first to use it.&lt;/p&gt;
&lt;h2 id="cool-idea-and-im-not-sure-id-ever-use-it"&gt;Cool idea and I'm not
sure I'd ever use it&lt;/h2&gt;
&lt;p&gt;Even at that project in Belgium: their own research found people were
confused by WebIDs and pods and that the main problem was
&lt;em&gt;explainability&lt;/em&gt;. Which is why every shipping version hides Solid
completely behind a normal-looking app like itsme.&lt;/p&gt;
&lt;h2 id="foaf-me"&gt;FOAF me&lt;/h2&gt;
&lt;p&gt;I’ve published a FOAF file, which looks kinda like this:&lt;/p&gt;
&lt;pre class="turtle"&gt;&lt;code&gt;@prefix foaf: &amp;lt;http://xmlns.com/foaf/0.1/&amp;gt; .
@prefix rdfs: &amp;lt;http://www.w3.org/2000/01/rdf-schema#&amp;gt; .

&amp;lt;#me&amp;gt; a foaf:Person ;
    foaf:name &amp;quot;Nathan Herald&amp;quot; ;
    foaf:homepage &amp;lt;https://nathanherald.com/&amp;gt; ;
    foaf:knows [
        a foaf:Person ;
        foaf:name &amp;quot;A Friend&amp;quot; ;
        rdfs:seeAlso &amp;lt;https://afriend.example/profile.ttl&amp;gt;
    ] .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It lives at &lt;a href="/profile.ttl"&gt;&lt;code&gt;/profile.ttl&lt;/code&gt;&lt;/a&gt;, so
&lt;a
href="https://nathanherald.com/profile.ttl#me"&gt;https://nathanherald.com/profile.ttl#me&lt;/a&gt;
is my WebID. That &lt;code&gt;#me&lt;/code&gt; fragment matters: the file is a
&lt;em&gt;document&lt;/em&gt;, and I'm a &lt;em&gt;person&lt;/em&gt; it describes.&lt;/p&gt;
&lt;p&gt;So no pod for me. Just a little file and I’m now a little node in the
web of people, pointing at my friends. Pretty cool.&lt;/p&gt;</description>
    </item>

    <item>
      <title>"The Taste of Tea (茶の味) Trailer"</title>
      <link>https://nathanherald.com/posts/links/taste-of-tea-trailer/</link>
      <pubDate>Sun, 21 Jun 2026 10:51:46 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/taste-of-tea-trailer/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/Da6FJH7IkNA" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;</description>
    </item>

    <item>
      <title>John Thomson’s China</title>
      <link>https://nathanherald.com/posts/links/john-thomsons-china/</link>
      <pubDate>Sat, 20 Jun 2026 09:20:33 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/john-thomsons-china/</guid>
      <description>&lt;p&gt;It’s amazing to see these photographs from so long ago.&lt;/p&gt;</description>
    </item>

    <item>
      <title>I’m tired of talking to AI</title>
      <link>https://nathanherald.com/posts/links/im-tired-of-talking-to-ai/</link>
      <pubDate>Sat, 30 May 2026 13:48:56 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/im-tired-of-talking-to-ai/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I’m tired of talking to AI. I want to talk to real people. But even
when I talk to people, they forward my questions to AI and send me the
AI’s answer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yep.&lt;/p&gt;</description>
    </item>

    <item>
      <title>"Reel: Overview Walkthrough (Part 1)"</title>
      <link>https://nathanherald.com/posts/links/reel-4-track-recorder/</link>
      <pubDate>Sat, 16 May 2026 16:43:35 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/reel-4-track-recorder/</guid>
      <description>&lt;p&gt;An awesome 4-track recorder for iOS.&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/ThpLVsb1fWQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;</description>
    </item>

    <item>
      <title>Open House</title>
      <link>https://nathanherald.com/posts/links/open-house/</link>
      <pubDate>Sat, 16 May 2026 15:17:57 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/open-house/</guid>
      <description>&lt;p&gt;📍 Coffee in Berlin.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Practical example of how cool RDF is</title>
      <link>https://nathanherald.com/posts/practical-rdf-example/</link>
      <pubDate>Fri, 15 May 2026 15:21:24 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/practical-rdf-example/</guid>
      <description>&lt;p&gt;Imagine we’ve ingested a ton of maritime data. (I couldn’t come up
with a better example today.)&lt;/p&gt;
&lt;p&gt;Someone asks us “Can shipment 8821 go through the Suez Canal?”&lt;/p&gt;
&lt;p&gt;Before: someone has to query different systems for regulations,
jurisdictions, routes, cargo manifests, and permits. JOIN them if
possible. Paste them into an LLM to synthesize, and hope the answer is
complete.&lt;/p&gt;
&lt;p&gt;What we &lt;em&gt;could&lt;/em&gt; do: we can ingest all our data into a semantic
knowledge system and let an agent query through at the semantic
level.&lt;/p&gt;
&lt;p&gt;Our agent might get a list of tools it can call:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Lots of JSON&lt;/summary&gt;

&lt;pre&gt;&lt;code&gt;{
  &amp;quot;actions&amp;quot;: [
    {
      &amp;quot;name&amp;quot;: &amp;quot;CanTransitAction&amp;quot;,
      &amp;quot;description&amp;quot;: &amp;quot;Determine whether a shipment may transit a jurisdiction under active maritime policy.&amp;quot;,
      &amp;quot;input&amp;quot;: {
        &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
        &amp;quot;required&amp;quot;: [&amp;quot;shipment&amp;quot;, &amp;quot;jurisdiction&amp;quot;, &amp;quot;validTime&amp;quot;],
        &amp;quot;properties&amp;quot;: {
          &amp;quot;shipment&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;IRI of the shipment entity.&amp;quot;,
            &amp;quot;examples&amp;quot;: [&amp;quot;cargo:shipment-8821&amp;quot;]
          },
          &amp;quot;jurisdiction&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;IRI of the jurisdiction entity.&amp;quot;,
            &amp;quot;examples&amp;quot;: [&amp;quot;jurisdiction:egypt-suez&amp;quot;]
          },
          &amp;quot;validTime&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
            &amp;quot;format&amp;quot;: &amp;quot;date-time&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;The real-world time for which the transit decision should be evaluated.&amp;quot;
          }
        },
        &amp;quot;additionalProperties&amp;quot;: false
      },
      &amp;quot;output&amp;quot;: {
        &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
        &amp;quot;required&amp;quot;: [&amp;quot;type&amp;quot;, &amp;quot;allowed&amp;quot;, &amp;quot;reason&amp;quot;, &amp;quot;citesClaim&amp;quot;, &amp;quot;usedPolicy&amp;quot;],
        &amp;quot;properties&amp;quot;: {
          &amp;quot;type&amp;quot;: {
            &amp;quot;const&amp;quot;: &amp;quot;CanTransitResult&amp;quot;
          },
          &amp;quot;allowed&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;
          },
          &amp;quot;reason&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
          },
          &amp;quot;citesClaim&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,
            &amp;quot;items&amp;quot;: { &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot; }
          },
          &amp;quot;usedPolicy&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
          }
        },
        &amp;quot;additionalProperties&amp;quot;: false
      }
    },
    {
      &amp;quot;name&amp;quot;: &amp;quot;ResolveEntityAction&amp;quot;,
      &amp;quot;description&amp;quot;: &amp;quot;Resolve a human phrase like &amp;#39;shipment 8821&amp;#39; or &amp;#39;Suez Canal&amp;#39; into a substrate IRI.&amp;quot;,
      &amp;quot;input&amp;quot;: {
        &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
        &amp;quot;required&amp;quot;: [&amp;quot;mention&amp;quot;, &amp;quot;expectedType&amp;quot;],
        &amp;quot;properties&amp;quot;: {
          &amp;quot;mention&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;The phrase supplied by the user.&amp;quot;,
            &amp;quot;examples&amp;quot;: [&amp;quot;shipment 8821&amp;quot;, &amp;quot;Suez Canal&amp;quot;]
          },
          &amp;quot;expectedType&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;The expected entity type.&amp;quot;,
            &amp;quot;enum&amp;quot;: [&amp;quot;Shipment&amp;quot;, &amp;quot;Jurisdiction&amp;quot;, &amp;quot;Port&amp;quot;, &amp;quot;Vessel&amp;quot;, &amp;quot;Permit&amp;quot;, &amp;quot;CargoType&amp;quot;]
          }
        },
        &amp;quot;additionalProperties&amp;quot;: false
      },
      &amp;quot;output&amp;quot;: {
        &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
        &amp;quot;required&amp;quot;: [&amp;quot;status&amp;quot;],
        &amp;quot;properties&amp;quot;: {
          &amp;quot;status&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
            &amp;quot;enum&amp;quot;: [&amp;quot;resolved&amp;quot;, &amp;quot;ambiguous&amp;quot;, &amp;quot;not_found&amp;quot;]
          },
          &amp;quot;iri&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Resolved entity IRI when status is resolved.&amp;quot;
          },
          &amp;quot;confidence&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;number&amp;quot;,
            &amp;quot;minimum&amp;quot;: 0,
            &amp;quot;maximum&amp;quot;: 1
          },
          &amp;quot;matchedBy&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,
            &amp;quot;items&amp;quot;: { &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot; },
            &amp;quot;examples&amp;quot;: [[&amp;quot;exact shipmentId&amp;quot;], [&amp;quot;label&amp;quot;, &amp;quot;alias&amp;quot;]]
          },
          &amp;quot;citedClaims&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,
            &amp;quot;items&amp;quot;: { &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot; }
          },
          &amp;quot;candidates&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Candidate entities when status is ambiguous.&amp;quot;,
            &amp;quot;items&amp;quot;: {
              &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
              &amp;quot;required&amp;quot;: [&amp;quot;iri&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;confidence&amp;quot;],
              &amp;quot;properties&amp;quot;: {
                &amp;quot;iri&amp;quot;: { &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot; },
                &amp;quot;label&amp;quot;: { &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot; },
                &amp;quot;confidence&amp;quot;: {
                  &amp;quot;type&amp;quot;: &amp;quot;number&amp;quot;,
                  &amp;quot;minimum&amp;quot;: 0,
                  &amp;quot;maximum&amp;quot;: 1
                }
              },
              &amp;quot;additionalProperties&amp;quot;: false
            }
          }
        },
        &amp;quot;additionalProperties&amp;quot;: false
      }
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;p&gt;The &lt;code&gt;CanTransitAction&lt;/code&gt; looks correct, but the agent needs
to first resolve the entities from plain text to IDs first:&lt;/p&gt;
&lt;div class="sourceCode" id="cb2"&gt;&lt;pre
class="sourceCode elixir"&gt;&lt;code class="sourceCode elixir"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;action&lt;span class="fu"&gt;(&lt;/span&gt;%&lt;span class="fu"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="va"&gt;action:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;ResolveEntityAction&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="va"&gt;expectedType:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Shipment&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb2-4"&gt;&lt;a href="#cb2-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="va"&gt;mention:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;shipment 8821&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-5"&gt;&lt;a href="#cb2-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="sourceCode" id="cb3"&gt;&lt;pre
class="sourceCode elixir"&gt;&lt;code class="sourceCode elixir"&gt;&lt;span id="cb3-1"&gt;&lt;a href="#cb3-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;action&lt;span class="fu"&gt;(&lt;/span&gt;%&lt;span class="fu"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-2"&gt;&lt;a href="#cb3-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="va"&gt;action:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;ResolveEntityAction&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb3-3"&gt;&lt;a href="#cb3-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="va"&gt;expectedType:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Jurisdiction&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb3-4"&gt;&lt;a href="#cb3-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="va"&gt;mention:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Suez Canal&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-5"&gt;&lt;a href="#cb3-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then it can try to run the action:&lt;/p&gt;
&lt;div class="sourceCode" id="cb4"&gt;&lt;pre
class="sourceCode elixir"&gt;&lt;code class="sourceCode elixir"&gt;&lt;span id="cb4-1"&gt;&lt;a href="#cb4-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;action&lt;span class="fu"&gt;(&lt;/span&gt;%&lt;span class="fu"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-2"&gt;&lt;a href="#cb4-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="st"&gt;&amp;quot;action&amp;quot;&lt;/span&gt;: &lt;span class="st"&gt;&amp;quot;CanTransitAction&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb4-3"&gt;&lt;a href="#cb4-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="st"&gt;&amp;quot;shipment&amp;quot;&lt;/span&gt;: &lt;span class="st"&gt;&amp;quot;cargo:shipment-8821&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb4-4"&gt;&lt;a href="#cb4-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="st"&gt;&amp;quot;jurisdiction&amp;quot;&lt;/span&gt;: &lt;span class="st"&gt;&amp;quot;jurisdiction:egypt-suez&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb4-5"&gt;&lt;a href="#cb4-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="st"&gt;&amp;quot;validTime&amp;quot;&lt;/span&gt;: &lt;span class="st"&gt;&amp;quot;2026-05-12T00:00:00Z&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-6"&gt;&lt;a href="#cb4-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pretty straightforward and it &lt;em&gt;should&lt;/em&gt; get back an answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But what is actually in our “database” though?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;claims&lt;/em&gt; we have might look like:&lt;/p&gt;
&lt;pre class="trig"&gt;&lt;code&gt;# @prefix declarations left out on purpose

GRAPH :CargoTransitPolicy-v12 {
  :CargoTransitPolicy-v12 a :TransitPolicy ;
    :blocksCargoType :SanctionedGoods ;
    :requiresPermit [
      :forCargoType :HazardousCargo ;
      :throughJurisdiction :EgyptSuez ;
      :permitType :SuezHazmatPermit ;
    ] .
}

GRAPH cargo:claim-100 {
  cargo:shipment-8821 a onto:Shipment ;
    onto:contains cargo:diesel-fuel ;
    onto:origin port:singapore ;
    onto:destination port:rotterdam .
}

GRAPH cargo:claim-101 {
  cargo:diesel-fuel a onto:HazardousCargo ;
    onto:hazardClass &amp;quot;flammable-liquid&amp;quot; .
}

GRAPH route:claim-200 {
  route:singapore-to-rotterdam onto:transitsThrough jurisdiction:egypt-suez .
}

GRAPH reg:claim-300 {
  jurisdiction:egypt-suez onto:allowsTransitOf onto:HazardousCargo ;
    onto:requiresPermit permit:suez-hazmat-permit .
}

GRAPH permit:claim-400 {
  cargo:shipment-8821 onto:hasPermit permit:suez-hazmat-permit-778 .
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We would likely persist “quads” which is a “triple” + named graph.
(&lt;a
href="https://sphn-semantic-framework.readthedocs.io/en/latest/background_information/nquads.html"&gt;RDF
Quads&lt;/a&gt; has a nice graphic that kinda helps illustrate this.)&lt;/p&gt;
&lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Semantic_triple"&gt;triple&lt;/a&gt;
is the smallest assertion in our system. A triple is always subject,
predicate, and object.
&lt;code&gt;Nathan → hasEmail → &amp;lt;myobie@duck.com&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That last quad in the above &lt;code&gt;.trig&lt;/code&gt; document says:
“shipment 8821 has permit 778.” By itself, it does not say what the
permit allows. The meaning comes from combining it with the other
claims: Suez requires a hazmat permit for hazardous cargo, the shipment
contains hazardous cargo, and the shipment has the required permit.
Being able to combine these claims lets us build up a world of facts
which we can then answer questions from.&lt;/p&gt;
&lt;p&gt;A quad is the smallest stored assertion: subject, predicate, object,
and named graph. In this type of system, a "claim" is typically a named
graph / claim packet containing one or more quads plus metadata such as
provenance, policy, validation, and signatures. The subjects and objects
are nodes. The predicates are the edges.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GRAPH&lt;/code&gt; names a collection of triples inside an RDF
dataset. The graph name itself is an IRI, so the system can make
statements about the graph elsewhere. A line ending in a &lt;code&gt;;&lt;/code&gt;
means the next line still applies to the same subject. A line ending in
&lt;code&gt;.&lt;/code&gt; means that’s it for this subject.&lt;/p&gt;
&lt;p&gt;Actions are also claims in the &lt;em&gt;database&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="trig"&gt;&lt;code&gt;GRAPH :CanTransitAction {
  :CanTransitAction a :ExecutableAction ;
  :inputShape :CanTransitActionShape ;
  :outputShape :CanTransitResultShape ;
  :runtime &amp;quot;elixir&amp;quot; ;
  :mfa &amp;quot;Maritime.Actions.CanTransit.run/2&amp;quot; ;
  :requiredCapability :CanEvaluateTransitPolicy ;
  :usesPolicy :CargoTransitPolicy-v12 .
}

GRAPH :ResolveEntityAction {
  :ResolveEntityAction a :ExecutableAction ;
    :inputShape :ResolveEntityInputShape ;
    :outputShape :ResolveEntityResultShape ;
    :runtime &amp;quot;elixir&amp;quot; ;
    :mfa &amp;quot;Substrate.Actions.ResolveEntity.run/2&amp;quot; ;
    :requiredCapability :CanResolveEntities .
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here is some elixir pseudo-code that the persisted MFA might
point to:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;`Maritime.Actions.CanTransit`&lt;/summary&gt;

&lt;div class="sourceCode" id="cb7"&gt;&lt;pre
class="sourceCode elixir"&gt;&lt;code class="sourceCode elixir"&gt;&lt;span id="cb7-1"&gt;&lt;a href="#cb7-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;defmodule&lt;/span&gt; &lt;span class="cn"&gt;Maritime&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="cn"&gt;Actions&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="cn"&gt;CanTransit&lt;/span&gt; &lt;span class="kw"&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-2"&gt;&lt;a href="#cb7-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="ot"&gt;@moduledoc &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-3"&gt;&lt;a href="#cb7-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;  Evaluates whether a shipment may transit a jurisdiction.&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-4"&gt;&lt;a href="#cb7-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb7-5"&gt;&lt;a href="#cb7-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;  Input shape:  :CanTransitActionShape&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-6"&gt;&lt;a href="#cb7-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;  Output shape: :CanTransitResultShape&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-7"&gt;&lt;a href="#cb7-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;  Policy:       :CargoTransitPolicy-v12&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-8"&gt;&lt;a href="#cb7-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;  &lt;/span&gt;&lt;span class="ot"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-9"&gt;&lt;a href="#cb7-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb7-10"&gt;&lt;a href="#cb7-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="kw"&gt;def&lt;/span&gt; run&lt;span class="fu"&gt;(&lt;/span&gt;input, ctx&lt;span class="fu"&gt;)&lt;/span&gt; &lt;span class="kw"&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-11"&gt;&lt;a href="#cb7-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    projection &lt;span class="op"&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-12"&gt;&lt;a href="#cb7-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;      ctx&lt;span class="op"&gt;.&lt;/span&gt;open_projection!&lt;span class="fu"&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-13"&gt;&lt;a href="#cb7-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="va"&gt;ontology:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;maritime-compliance-v4&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-14"&gt;&lt;a href="#cb7-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="va"&gt;valid_time:&lt;/span&gt; input&lt;span class="op"&gt;.&lt;/span&gt;validTime,&lt;/span&gt;
&lt;span id="cb7-15"&gt;&lt;a href="#cb7-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="va"&gt;capability_envelope:&lt;/span&gt; ctx&lt;span class="op"&gt;.&lt;/span&gt;capabilities&lt;/span&gt;
&lt;span id="cb7-16"&gt;&lt;a href="#cb7-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;      &lt;span class="fu"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-17"&gt;&lt;a href="#cb7-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb7-18"&gt;&lt;a href="#cb7-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    shipment &lt;span class="op"&gt;=&lt;/span&gt; projection&lt;span class="op"&gt;.&lt;/span&gt;entity!&lt;span class="fu"&gt;(&lt;/span&gt;input&lt;span class="op"&gt;.&lt;/span&gt;shipment&lt;span class="fu"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-19"&gt;&lt;a href="#cb7-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    jurisdiction &lt;span class="op"&gt;=&lt;/span&gt; projection&lt;span class="op"&gt;.&lt;/span&gt;entity!&lt;span class="fu"&gt;(&lt;/span&gt;input&lt;span class="op"&gt;.&lt;/span&gt;jurisdiction&lt;span class="fu"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-20"&gt;&lt;a href="#cb7-20" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb7-21"&gt;&lt;a href="#cb7-21" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    cargo_types &lt;span class="op"&gt;=&lt;/span&gt; projection&lt;span class="op"&gt;.&lt;/span&gt;types_for_contained_cargo&lt;span class="fu"&gt;(&lt;/span&gt;shipment&lt;span class="fu"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-22"&gt;&lt;a href="#cb7-22" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    permits &lt;span class="op"&gt;=&lt;/span&gt; projection&lt;span class="op"&gt;.&lt;/span&gt;permits_for&lt;span class="fu"&gt;(&lt;/span&gt;shipment&lt;span class="fu"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-23"&gt;&lt;a href="#cb7-23" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb7-24"&gt;&lt;a href="#cb7-24" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    required_permit &lt;span class="op"&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-25"&gt;&lt;a href="#cb7-25" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;      projection&lt;span class="op"&gt;.&lt;/span&gt;required_permit_for&lt;span class="fu"&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-26"&gt;&lt;a href="#cb7-26" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="va"&gt;cargo_types:&lt;/span&gt; cargo_types,&lt;/span&gt;
&lt;span id="cb7-27"&gt;&lt;a href="#cb7-27" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="va"&gt;jurisdiction:&lt;/span&gt; jurisdiction,&lt;/span&gt;
&lt;span id="cb7-28"&gt;&lt;a href="#cb7-28" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="va"&gt;policy:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;:CargoTransitPolicy-v12&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-29"&gt;&lt;a href="#cb7-29" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;      &lt;span class="fu"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-30"&gt;&lt;a href="#cb7-30" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb7-31"&gt;&lt;a href="#cb7-31" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="cf"&gt;cond&lt;/span&gt; &lt;span class="kw"&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-32"&gt;&lt;a href="#cb7-32" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;      required_permit &lt;span class="op"&gt;&amp;amp;&amp;amp;&lt;/span&gt; required_permit &lt;span class="kw"&gt;not&lt;/span&gt; &lt;span class="kw"&gt;in&lt;/span&gt; permits &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-33"&gt;&lt;a href="#cb7-33" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        %&lt;span class="fu"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-34"&gt;&lt;a href="#cb7-34" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;          &lt;span class="va"&gt;type:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;:CanTransitResult&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-35"&gt;&lt;a href="#cb7-35" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;          &lt;span class="va"&gt;allowed:&lt;/span&gt; &lt;span class="cn"&gt;false&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-36"&gt;&lt;a href="#cb7-36" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;          &lt;span class="va"&gt;reason:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Shipment requires &lt;/span&gt;&lt;span class="ot"&gt;#{&lt;/span&gt;required_permit&lt;span class="ot"&gt;}&lt;/span&gt;&lt;span class="st"&gt; for transit through &lt;/span&gt;&lt;span class="ot"&gt;#{&lt;/span&gt;input&lt;span class="op"&gt;.&lt;/span&gt;jurisdiction&lt;span class="ot"&gt;}&lt;/span&gt;&lt;span class="st"&gt;.&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-37"&gt;&lt;a href="#cb7-37" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;          &lt;span class="va"&gt;citesClaim:&lt;/span&gt; &lt;span class="ot"&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-38"&gt;&lt;a href="#cb7-38" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="st"&gt;&amp;quot;cargo:claim-100&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-39"&gt;&lt;a href="#cb7-39" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="st"&gt;&amp;quot;cargo:claim-101&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-40"&gt;&lt;a href="#cb7-40" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="st"&gt;&amp;quot;route:claim-200&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-41"&gt;&lt;a href="#cb7-41" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="st"&gt;&amp;quot;reg:claim-300&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-42"&gt;&lt;a href="#cb7-42" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;          &lt;span class="ot"&gt;]&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-43"&gt;&lt;a href="#cb7-43" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;          &lt;span class="va"&gt;usedPolicy:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;:CargoTransitPolicy-v12&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-44"&gt;&lt;a href="#cb7-44" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="fu"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-45"&gt;&lt;a href="#cb7-45" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb7-46"&gt;&lt;a href="#cb7-46" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;      &lt;span class="cn"&gt;true&lt;/span&gt; &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-47"&gt;&lt;a href="#cb7-47" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        %&lt;span class="fu"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-48"&gt;&lt;a href="#cb7-48" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;          &lt;span class="va"&gt;type:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;:CanTransitResult&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-49"&gt;&lt;a href="#cb7-49" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;          &lt;span class="va"&gt;allowed:&lt;/span&gt; &lt;span class="cn"&gt;true&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-50"&gt;&lt;a href="#cb7-50" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;          &lt;span class="va"&gt;reason:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Shipment may transit because all required permits are present.&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-51"&gt;&lt;a href="#cb7-51" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;          &lt;span class="va"&gt;citesClaim:&lt;/span&gt; &lt;span class="ot"&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-52"&gt;&lt;a href="#cb7-52" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="st"&gt;&amp;quot;cargo:claim-100&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-53"&gt;&lt;a href="#cb7-53" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="st"&gt;&amp;quot;cargo:claim-101&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-54"&gt;&lt;a href="#cb7-54" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="st"&gt;&amp;quot;route:claim-200&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-55"&gt;&lt;a href="#cb7-55" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="st"&gt;&amp;quot;reg:claim-300&amp;quot;&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-56"&gt;&lt;a href="#cb7-56" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="st"&gt;&amp;quot;permit:claim-400&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-57"&gt;&lt;a href="#cb7-57" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;          &lt;span class="ot"&gt;]&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb7-58"&gt;&lt;a href="#cb7-58" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;          &lt;span class="va"&gt;usedPolicy:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;:CargoTransitPolicy-v12&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-59"&gt;&lt;a href="#cb7-59" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="fu"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-60"&gt;&lt;a href="#cb7-60" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-61"&gt;&lt;a href="#cb7-61" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;  &lt;span class="kw"&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-62"&gt;&lt;a href="#cb7-62" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;

&lt;p&gt;The result of the action can itself become a claim:&lt;/p&gt;
&lt;pre class="trig"&gt;&lt;code&gt;GRAPH :result-900 {
  :result-900 a :CanTransitResult ;
    :allowed true ;
    :reason &amp;quot;Shipment 8821 may transit because hazardous cargo is allowed with the required Suez hazmat permit.&amp;quot; ;
    :citesClaim cargo:claim-100 ;
    :citesClaim cargo:claim-101 ;
    :citesClaim route:claim-200 ;
    :citesClaim reg:claim-300 ;
    :citesClaim permit:claim-400 ;
    :usedPolicy :CargoTransitPolicy-v12 .
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So it’s claims all the way down.&lt;/p&gt;
&lt;p&gt;By making everything in the system a claim we build up a world that
is coherent and governed. Claims can describe policies and capabilities,
and claims can also be governed by policies and capabilities. Which
means every actor can only move through the substrate with their given
capabilities: human or agent.&lt;/p&gt;
&lt;p&gt;And to take it a bit further: an agent can “tend the garden of
shapes” to help keep things tidy and coherent. If the system were built
correctly, everything an agent does would be reviewable and reversible.
Then all queries can operate at a well curated semantic layer and return
real, trustable results.&lt;/p&gt;
&lt;p&gt;This maritime example might seem far from your use cases, but this is
applicable even for very personal data. Scanning PDFs and emails of all
your medical records to build up the facts of your medical history,
household knowledge base, job search / placement projects, etc. Pretty
cool.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Mastodon kinda hates AI</title>
      <link>https://nathanherald.com/posts/mastodon-kinda-hates-ai/</link>
      <pubDate>Thu, 14 May 2026 11:51:57 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/mastodon-kinda-hates-ai/</guid>
      <description>&lt;p&gt;Mastodon, as a network, leans pretty strongly anti-AI. Not just the
people I follow, but the network overall. It’s something I see every
day, but today I saw a very clear single example.&lt;/p&gt;
&lt;p&gt;&lt;a
href="https://1password.social/@1password/116569353856438624"&gt;1Password
posted a link to a blog post about how they use AI for backend dev on
Masto&lt;/a&gt; which showed up in my feed and I immediately noticed the
amount of negative replies: 42 replies, 41 of which were negative. Here
is a representative slice, paraphrased so I’m not singling anyone
out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“No. Just no.”&lt;/li&gt;
&lt;li&gt;Equating any AI use with deliberately introducing security
flaws.&lt;/li&gt;
&lt;li&gt;“Stochastic parrots.” “AI slop.” “Grey goo.”&lt;/li&gt;
&lt;li&gt;“The ideal agent-to-human ratio is 0.”&lt;/li&gt;
&lt;li&gt;A lot of “I’m canceling my subscription” / “guess I need to find a
new password manager.”&lt;/li&gt;
&lt;li&gt;“Set itself on fire in the race to shove AI into everything.”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I went to see if they posted that blog link anywhere else. They
posted the same blog post link &lt;a
href="https://www.linkedin.com/posts/1password_ai-softwareengineering-agenticai-activity-7458229249091612672-luun"&gt;on
LinkedIn&lt;/a&gt; and &lt;a
href="https://x.com/1Password/status/2046397330396692940"&gt;on X&lt;/a&gt;, but
not on their Threads or Bluesky accounts. There are a lot fewer replies
on both and they are mostly just engaging with the topic or asking for
support.&lt;/p&gt;
&lt;p&gt;I mainly read through Mastodon to learn about cool tech stuff + a lot
of my real-life friends are on there, but Masto seems to be getting more
anti-tech in general these days and I am finding less cool stuff there
recently.&lt;/p&gt;
&lt;p&gt;I find Threads is better for photography and general interest. How
weird that photographers have migrated over to Threads from Instagram
since Insta is all about Reels now…&lt;/p&gt;
&lt;p&gt;The norms building up inside the post-twitter text-based social
networks are fascinating to watch. I honestly never would have assumed a
large network could have visible biases like this.&lt;/p&gt;
&lt;p&gt;Is there an online space that is pro-“tech to help people”? Or
pro-this-stuff-is-cool-check-it-out? Or just generally biased towards
constructive convos?&lt;/p&gt;</description>
    </item>

    <item>
      <title>"1990: Whatever Happened to the Cameras of the Future? | Tomorrow’s World | BBC Archive"</title>
      <link>https://nathanherald.com/posts/links/cameras-of-the-future-1990/</link>
      <pubDate>Sun, 10 May 2026 21:31:35 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/cameras-of-the-future-1990/</guid>
      <description>&lt;p&gt;So fun to see.&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/iCtdWcgcY3Q" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;</description>
    </item>

    <item>
      <title>"Ozu in Color – TCM x Every Frame a Painting | TCM Originals"</title>
      <link>https://nathanherald.com/posts/links/efap-ozu-in-color/</link>
      <pubDate>Thu, 07 May 2026 18:37:54 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/efap-ozu-in-color/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;We are so lucky that Every Frame a Painting is back.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/s5P22nEmF3k" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;</description>
    </item>

    <item>
      <title>Principles matter more with agents</title>
      <link>https://nathanherald.com/posts/principles-driven-development/</link>
      <pubDate>Tue, 05 May 2026 00:13:18 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/principles-driven-development/</guid>
      <description>&lt;p&gt;Whether working with humans or robots, it always helps to have your
core principles documented and defined. I find most programmers think
the specific architecture, data structures, or the language matters most
and should be well defined for all projects… but I disagree.&lt;/p&gt;
&lt;p&gt;What matters to me are the core principles and conventions. How do we
agree we can read from state safely? How do we agree to mutate state, if
at all? What sort of reactivity is important to us and how do we agree
to use reactivity in our UI and elsewhere? Where should we keep shared
code? How important is sharing code vs. isolation? What defines a unit
test? Can unit tests use the disk, the database, the network? How do we
avoid mocking everything so we know our app works without having slow
tests? Can we tolerate any failing or skipped tests?&lt;/p&gt;
&lt;p&gt;If these things aren’t defined, then it doesn’t matter if you choose
go or javascript, your app will most likely be buggy and not
understandable.&lt;/p&gt;
&lt;p&gt;It’s easier to talk about an example to show what I mean.&lt;/p&gt;
&lt;p&gt;When building an iOS app with an agent, I find setting some base
principles around state to be the most important.&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;All visible state must be exposed through AppState which must be
@Observable. AppState must contain and expose literally all visible
state, no exceptions. If it is shown, it must be in AppState.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This takes care of how we read from state to present it, how that
state is reactive, and helps us pre-decide a ton of things. Inside
AppState we keep state as simple structs and enums. If we want to
debounce or rate limit state updates, we debounce or rate limit
assignments to parts of AppState, we don’t mess with how @Observable
works. Etc. Also, AppState is fully unit testable. We can setup and test
any version of AppState in a test, which means we can test any version
of our app that the user would see. And creating Previews from AppState
will be super straightforward.&lt;/p&gt;
&lt;ol start="2" type="1"&gt;
&lt;li&gt;State can only be mutated by methods on AppActions, which has
init(appState: AppState). By convention, nothing else can mutate state
held by AppState. All methods on AppActions must be unit tested.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;AppActions should be super easy to unit test. It should be easy to
setup any version of an AppState and then test how a method of
AppActions changes it. Which leads me to…&lt;/p&gt;
&lt;ol start="3" type="1"&gt;
&lt;li&gt;The UI can only call methods on AppActions. So views must take
init(appActions: AppActions) or use @Environment.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since AppActions is super unit testable and the UI can only ever call
methods on AppActions, then &amp;gt;90% of what the UI does can be unit
tested. You can be confident most of the app works just from the
tests.&lt;/p&gt;
&lt;p&gt;This is super super important when working with others you don’t
trust. And a coding agent is a developer you cannot trust 😅&lt;/p&gt;
&lt;ol start="4" type="1"&gt;
&lt;li&gt;All things like date formatters, string formatters, etc must be
methods on AppHelpers. All methods on AppHelpers must be unit
tested.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This should make sense.&lt;/p&gt;
&lt;ol start="5" type="1"&gt;
&lt;li&gt;Local first, offline first all the time.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Don’t ever allow a coding agent to start building some sort of
&lt;em&gt;online only&lt;/em&gt; feature. If you want to show something from a
remote server, expose it via AppState. Then have a way to read from disk
into AppState. Then have a way to fetch from the network and write to
disk. And now you can hit the remote server, write to disk, assign into
AppState, and the UI reacts automatically and everyone is good. Leave
the old version cached on disk if at all possible. Don’t revert to an
empty state while fetching. Keep some sort of isFetching in AppState if
you need to. Even better: model your state machine with an enum so you
can transition through different states which drive the UI. And unit
test everything.&lt;/p&gt;
&lt;p&gt;Even with strong principles defined, you have to review the agent’s
work regularly and make sure it’s still adhering. Drift happens. An
agent will start mocking things it shouldn’t, mutating state from
outside AppActions, or sneaking in some online-only call. Catch it early
and pull the work back in line.&lt;/p&gt;
&lt;p&gt;This is enough to get started. And sure, one could say SwiftUI only,
Swift 6.2 only, MainActor by default, and one can even start talking
about data modeling and schemas… but those should be defined in a
different file.&lt;/p&gt;
&lt;p&gt;The above, and more, should go into a PRINCIPLES.md file that doesn’t
try to be a plan or a spec, but instead can help us build a reliable,
resilient app from any plan or spec.&lt;/p&gt;
&lt;p&gt;Also, don’t just go from PRINCIPLES.md to SPEC.md, write an IDEA.md
and make sure it’s clear that the idea is flexible, it may change as we
iterate. Document the problem + the big idea, those likely won’t change.
The plan will.&lt;/p&gt;
&lt;p&gt;Locking into a super detailed spec too early can prevent you from
discovering the best solutions to your problems. This is the same when
working with humans or coding agents.&lt;/p&gt;
&lt;p&gt;Instead define your core principles, your big idea, the problems you
are trying to solve, and then maybe some light specs.&lt;/p&gt;</description>
    </item>

    <item>
      <title>"Blackwater Holylight – Full Performance (Live on KEXP)"</title>
      <link>https://nathanherald.com/posts/links/blackwater-holylight-kexp/</link>
      <pubDate>Wed, 29 Apr 2026 22:28:05 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/blackwater-holylight-kexp/</guid>
      <description>&lt;p&gt;🎧&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/beQXLk_6LJE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;</description>
    </item>

    <item>
      <title>"Casio SXC-1 portable sampler"</title>
      <link>https://nathanherald.com/posts/links/casio-sxc-1/</link>
      <pubDate>Thu, 23 Apr 2026 19:05:40 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/casio-sxc-1/</guid>
      <description>&lt;p&gt;Let’s go! So cool.&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/GixrLNO3wOs" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;</description>
    </item>

    <item>
      <title>Making Chrome livable</title>
      <link>https://nathanherald.com/posts/making-chrome-livable/</link>
      <pubDate>Sun, 19 Apr 2026 20:29:19 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/making-chrome-livable/</guid>
      <description>&lt;p&gt;I’ve used Safari consistently since it came out. I’ve only maybe used
something different like &lt;a
href="https://en.wikipedia.org/wiki/OmniWeb"&gt;OmniWeb&lt;/a&gt; or &lt;a
href="https://en.wikipedia.org/wiki/Camino_%28web_browser%29"&gt;Camino&lt;/a&gt;
or Firefox or whatever, but always returning to Safari.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Enter vertical tabs in Chrome.&lt;/strong&gt; I’ve wanted vertical
tabs in my browser since OmniWeb. Chrome’s version isn’t perfect, but it
is very good. Combined with split views in windows and I am a happy
camper. I’ve been using Chrome Beta to get access to the vertical tabs
(which should be shipped out in regular Chrome now) for weeks and I love
them.&lt;/p&gt;
&lt;p&gt;But there are 3 things I miss from Safari and I &lt;em&gt;had&lt;/em&gt; to find
a way to fix them.&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;&lt;p&gt;&lt;a
href="https://github.com/myobie/translate-selected-chrome-extension"&gt;Right
click, Translate. Fixed with my own extension.&lt;/a&gt; Chrome wants to
translate a whole page, but it misses iframes, dynamic content, etc. I
almost always only want to translate a small part and in Safari I can
select, right click, and Translate. Well, now I can do the same in
Chrome using my own extension.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When a link opens a new tab in Safari, the back button is enabled
and click back closes the new tab and returns to the original tab.
Apparently this is something I use 100s of times a day and it has driven
me crazy. So I made a &lt;a
href="https://github.com/myobie/back-to-opener-chrome-extension"&gt;back to
opener chrome extension&lt;/a&gt; that does exactly this. This has made things
sooo much better for me.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I miss 1Blocker. It’s my fav content blocker and I want to have
it in Chrome as well. So I built &lt;a
href="https://github.com/myobie/unofficial-1blocker-rule-converter-for-chrome"&gt;a
python script to analyze the rules inside 1Blocker.app and churn out a
custom chrome extension&lt;/a&gt; that contains basically the same rules.
After some trial and error, it works!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Fixing these three things has made Chrome really livable for me. They
also have synced tab groups, just like Safari, so that works too.&lt;/p&gt;
&lt;p&gt;Chrome still doesn’t feel like a Mac app, it feels like a Linux app
running on my Mac, but it is much closer to my ideal browser now. I’ve
been daily-ing Chrome for a few weeks and I anticipate keeping it as my
default browser for many more.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Same beans, different cups</title>
      <link>https://nathanherald.com/posts/same-beans-different-cups/</link>
      <pubDate>Sun, 19 Apr 2026 16:39:31 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/same-beans-different-cups/</guid>
      <description>&lt;p&gt;I was hunting for a &lt;a href="https://melodrip.com/"&gt;Melodrip&lt;/a&gt; and
couldn’t find one in Europe without paying 40 to 50 bucks for shipping.
Kaffebox had one, and while I was there I saw the &lt;a
href="https://www.kaffebox.no/nordics-best-roaster-tasting-set/"&gt;Nordics
Best Roaster&lt;/a&gt; tasting set and added it to the cart too.&lt;/p&gt;
&lt;p&gt;Ten 50g tubes, one per finalist, all roasted from the exact same
green beans. I’ve been running them through my Hario Switch, V60
style.&lt;/p&gt;
&lt;p&gt;The fruitiness is all over the map. Some are explosively fruity,
others hardly fruity at all. The roaster’s share of the final taste is
way bigger than I would have guessed.&lt;/p&gt;
&lt;p&gt;First tasting set for me. The Sourced Coffee set is next, where each
roaster picks their own beans. Can’t wait.&lt;/p&gt;</description>
    </item>

    <item>
      <title>"The Film Everyone Passed On | The ROBOCOP Commentary"</title>
      <link>https://nathanherald.com/posts/links/robocop-commentary/</link>
      <pubDate>Fri, 17 Apr 2026 17:41:38 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/robocop-commentary/</guid>
      <description>&lt;p&gt;Let’s go!&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/6C7zB3UZCRE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;</description>
    </item>

    <item>
      <title>The Affordable Car Is Dead</title>
      <link>https://nathanherald.com/posts/links/affordable-car-is-dead/</link>
      <pubDate>Wed, 15 Apr 2026 10:59:11 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/affordable-car-is-dead/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The average transaction price for a new car now sits around
$50,000.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Today, Detroit doesn’t need the average American to buy a car; better
to sell affluent households their second S.U.V.&lt;/p&gt;
&lt;/blockquote&gt;</description>
    </item>

    <item>
      <title>"Bill Withers – Ain’t No Sunshine (BBC In Concert, 1974)"</title>
      <link>https://nathanherald.com/posts/links/aint-no-sunshine/</link>
      <pubDate>Mon, 13 Apr 2026 15:38:55 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/aint-no-sunshine/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/y3_Ym672_lU" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;</description>
    </item>

    <item>
      <title>"THE BLADE (1995): The Edges of Wuxia"</title>
      <link>https://nathanherald.com/posts/links/every-frame-a-painting-the-blade/</link>
      <pubDate>Sun, 12 Apr 2026 11:40:57 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/every-frame-a-painting-the-blade/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/ttehPZsNcxI" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Always great to see another Every Frame A Painting, it makes a
&lt;em&gt;ton&lt;/em&gt; of sense for Criterion to work with Tony, and the
comparison of kung fu movies to westerns is so good!&lt;/p&gt;</description>
    </item>

    <item>
      <title>How I use AI daily, April 2026</title>
      <link>https://nathanherald.com/posts/how-i-use-ai-daily-april-2026/</link>
      <pubDate>Sun, 12 Apr 2026 12:30:43 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/how-i-use-ai-daily-april-2026/</guid>
      <description>&lt;p&gt;I keep a list in Reminders for every project I work on. When I sit
down in the morning I scan through and pick what to work on. Usually I
make a git worktree for the task, spin up &lt;a
href="https://claude.ai/code"&gt;claude code&lt;/a&gt;, and ask it to propose how
best to achieve what I’m after. Then I decide: am I writing code or is
the agent spiking it out?&lt;/p&gt;
&lt;p&gt;All of my work sessions run inside &lt;a
href="https://github.com/myobie/pty"&gt;pty&lt;/a&gt;, a tool I built for
persistent terminal sessions. I can detach when I switch or take a
break, reattach later, and the conversation is right where I left it and
reminding me what I was doing. I jump between pty sessions throughout
the day as things need attention – even remotely from my MacBook Air and
iPhone.&lt;/p&gt;
&lt;p&gt;Some days I have ten things going like a reactor pattern, bouncing
between sessions all day. Some days I go deep on one thing and only
touch the other sessions during breaks. I let the work decide the shape.
It takes real mental energy to context switch this much, like playing
multiple chess boards at once. But the sessions are patient. They will
be there when I have brain space freed up.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I rarely run &lt;a href="https://github.com/openai/codex"&gt;codex&lt;/a&gt;
directly these days, I use it as a sub-agent. I have a skill configured
in claude code to let claude drive codex as a sub-agent. Codex is better
at unwinding very complex code or noticing nuanced things, so I let
claude dispatch to it and report back the results. My main context stays
at the project level and most of the heavy work happens in sub-agents
underneath.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;While I started, like everyone else, trying to build up docs and
skills and workflows to prevent claude from making mistakes, I’ve ended
up relaxing almost all of it. I’ve removed most of my CLAUDE.md files.
Why do I need docs for the robot and not for me? Now I document things
for myself and the robot can read those when it needs to.&lt;/p&gt;
&lt;p&gt;Everything is context management now. The transformer architecture
computes attention weights across the entire context to decide what to
generate next. Every token in the context influences the probability
distribution of the next token. So when you carefully set up the
context, clear problem statement, relevant code, good examples, prior
conversation, you are literally shifting the probability distribution
toward the output you want.&lt;/p&gt;
&lt;p&gt;You are not actually &lt;em&gt;talking&lt;/em&gt; to claude. You are attempting
to drag the weight of the context into the right starting point so it
generates next tokens appropriately to solve your problem. A longer,
more detailed context is heavier to drag but lands more precisely. A
short vague prompt is light but lands anywhere. The skill is knowing how
much context to load and what kind.&lt;/p&gt;
&lt;p&gt;&lt;a
href="https://simonwillison.net/2025/jun/27/context-engineering/"&gt;Context&lt;/a&gt;
&lt;a
href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents"&gt;engineering&lt;/a&gt;
is &lt;a href="https://www.philschmid.de/context-engineering"&gt;real&lt;/a&gt; and
it is a skill, just like some people are better at googling than
others.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I wrote about this on my &lt;a href="/ai/#tiers"&gt;AI page&lt;/a&gt;, but the
short version: I run every project at the level of automation that fits
it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tier 1, Hands Off.&lt;/strong&gt; I am the CEO, the agent is the
developer. I never read the code. My personal journal app, my podcast
listening app, a handful of CLI tools. The code is not the artifact. The
experience of using it is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tier 2, Hands On.&lt;/strong&gt; Anything that ships to other
people. &lt;a href="https://new.space/"&gt;new.space&lt;/a&gt; is in this tier. My
cofounder and I review everything carefully. Sometimes I write the code,
sometimes the agent spikes it, sometimes we throw the spike away and
start over properly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tier 3, Hands Only.&lt;/strong&gt; I write near 90% of the code
myself because I want to learn. The agent watches, answers questions,
handles tedium.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;My side projects in Tier 1 simply would not exist. A podcast
listening app, a personal journal, an image EXIF data manager. I
described what I wanted, the agent built them, and I use them every day.
I have never looked at the source code for any of them.&lt;/p&gt;
&lt;p&gt;For &lt;a href="https://new.space/"&gt;new.space&lt;/a&gt; we have a JavaScript
file that runs on the page in Safari when someone uses the iOS Share
Sheet. It extracts metadata from the page, creates a readable version,
handles all the edge cases. Building and testing across dozens of
different sites would have been too much effort for our small two-person
team, but letting claude hack on it and prove to us it was working made
it a project we could actually do. A real increase in quality of life
for our users.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Today I was working on a potential approval flow for dangerous tool
calls in &lt;a href="https://new.space/"&gt;new.space&lt;/a&gt; (like "send an email
for me"). I pushed claude to build it and use &lt;a
href="https://github.com/nicklama/xcodebuildmcp"&gt;xcodebuildmcp&lt;/a&gt; to
actually test it in the iOS Simulator and prove to me that it works.
Then I can try the UI concept myself, give feedback, let it go again.
Later today I will test the prototype on my real phone and decide if
this is even the direction I want to go.&lt;/p&gt;
&lt;p&gt;I do not have to waste time getting the details right just to figure
out if the direction is correct. I can have a tireless agent implement
it, try it, then decide. And I always have the agent document what it
learned and what it would do differently if it had the chance, so if I
want to start over I am not starting from zero.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I am building a web crawler and search engine in elixir by hand. I
want to combine it with a &lt;a href="https://dumps.wikimedia.org/"&gt;full
download of Wikipedia&lt;/a&gt; for a local offline agent. Crawling millions
of sites with a Mac Mini is very possible these days, and with an
external drive I think I can easily get to hundreds of millions of
pages. I will seed it with sites I trust and it crawls out from there so
the inputs are higher quality than trying to index the entire
internet.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I have built a couple of prototypes for my own agent harness, but
nothing good yet. One is in Swift. I think building in a native language
and talking to local models natively (not over HTTP) might be a huge
advantage, but it limits extensibility and contributions from
others.&lt;/p&gt;
&lt;p&gt;claude code is my favorite harness right now, but it does a lot of
things I probably do not need for every task and might not be optimized
for a smaller local model.&lt;/p&gt;
&lt;p&gt;For running local models, we built &lt;a
href="https://github.com/shareup/shllm"&gt;SHLLM&lt;/a&gt;, a Swift library for
talking to models directly. I also use &lt;a
href="https://ollama.com/"&gt;ollama&lt;/a&gt; for models too big for my current
machine. Qwen 3.5 and Gemma 4 are both genuinely good locally. I plan to
get an M5 Mac Studio later this year so I can run larger models
myself.&lt;/p&gt;
&lt;p&gt;My goal: a local-first coding agent that handles 80 to 90 percent of
my day-to-day by the end of this year.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I enjoy programming, but it is not important to me to preserve it as
"my job." Typing has never been the goal. Anyone who thinks they should
fight for the right to type is not on my side. I am fine with someone
else typing. I am not 100% thrilled about being a manager of robots, but
it is possible to make it fun and keep it lively with a bit of
enthusiasm.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The worst things that happen with agents are when they touch git. For
most projects I handle all the git stuff myself. I do not let the robot
do it unless it is a throwaway project. Git is hard for most humans and
I would not trust them to use it for me either.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I have been pretty clear eyed about AI. I am always skeptical of
claims until I see evidence. When I saw claude code when it first came
out I knew I needed to try it myself. And when it was able to do a few
things I asked it to do, completely by itself, I knew I could mold it
into a lot of other projects.&lt;/p&gt;
&lt;p&gt;A random text generator running fast enough can do anything with a
verification loop. &lt;a
href="https://en.wikipedia.org/wiki/Infinite_monkey_theorem"&gt;Monkeys on
typewriters writing Shakespeare.&lt;/a&gt; The verification loop is what makes
it real.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This is how I use AI today and I expect things to evolve and change
as the tools and models evolve and change. If someone is telling you
that AI can’t do anything useful, ignore them. If someone is telling you
that AI can do everything, ignore them. Both types of people do not have
your best interest in mind.&lt;/p&gt;
&lt;p&gt;Try one of the high quality AI coding agent harnesses with a capable
model and see for yourself. Be aware: free or low paid tiers are not
good enough yet. You are getting worse models running on worse hardware
with worse limits. This is unfortunate right now and part of why I am
pushing to build a local-first, offline-first setup for myself that is
capable.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Too Old Not To</title>
      <link>https://nathanherald.com/posts/links/too-old-not-to/</link>
      <pubDate>Fri, 10 Apr 2026 13:07:21 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/too-old-not-to/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/ljp5H2pjAFc" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Inspiring.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Artemis II Launches to the Moon</title>
      <link>https://nathanherald.com/posts/links/artemis-ii/</link>
      <pubDate>Wed, 01 Apr 2026 13:24:33 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/artemis-ii/</guid>
      <description>&lt;p&gt;Humans are heading back to the Moon. How incredible.&lt;/p&gt;</description>
    </item>

    <item>
      <title>airdrop CLI</title>
      <link>https://nathanherald.com/posts/airdrop-cli/</link>
      <pubDate>Sat, 28 Mar 2026 13:09:14 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/airdrop-cli/</guid>
      <description>&lt;p&gt;I made a tiny macOS CLI that opens the AirDrop sharing flow from the
terminal. Just &lt;code&gt;airdrop file.txt&lt;/code&gt; and the system share sheet
pops up.&lt;/p&gt;
&lt;video width="560" controls&gt;
&lt;source src="demo.mp4" type="video/mp4"&gt;
&lt;/video&gt;

&lt;p&gt;It’s a few lines of Swift, built with SwiftPM. Install it with
&lt;code&gt;./install.sh&lt;/code&gt; and it drops a binary into
&lt;code&gt;~/bin/airdrop&lt;/code&gt;.&lt;/p&gt;
&lt;div class="sourceCode" id="cb1"&gt;&lt;pre
class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;airdrop&lt;/span&gt; /path/to/file&lt;/span&gt;
&lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;airdrop&lt;/span&gt; /path/to/file1 /path/to/file2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Source on &lt;a href="https://github.com/myobie/airdrop"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</description>
    </item>

    <item>
      <title>ひとひら - 円 (Official Music Video)</title>
      <link>https://nathanherald.com/posts/links/hitohira-en/</link>
      <pubDate>Sat, 28 Mar 2026 11:48:29 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/hitohira-en/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/FD5hmkcXLnI" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;I’ve been jamming to this all morning.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Underwater Volcano Eruption (Kavachi)</title>
      <link>https://nathanherald.com/posts/links/kavachi-volcano/</link>
      <pubDate>Fri, 27 Mar 2026 00:20:19 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/kavachi-volcano/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/rK00tvzJ1Yc" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;I’ve never seen that underwater steam/lightning before 😲&lt;/p&gt;</description>
    </item>

    <item>
      <title>This ‘89 Pistons Cable Outro Goes Unnecessarily Hard</title>
      <link>https://nathanherald.com/posts/links/89-pistons-outro/</link>
      <pubDate>Wed, 25 Mar 2026 23:08:32 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/89-pistons-outro/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/mCm1SMaL1YY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;em&gt;This really does go hard.&lt;/em&gt;&lt;/p&gt;</description>
    </item>

    <item>
      <title>"TIL: at, atq, and atrun"</title>
      <link>https://nathanherald.com/posts/til/at-atq-atrun/</link>
      <pubDate>Wed, 25 Mar 2026 00:14:26 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/til/at-atq-atrun/</guid>
      <description>&lt;p&gt;Today I learned about &lt;code&gt;at&lt;/code&gt;, a built-in Unix command that
schedules a one-off command to run at a specific time. Unlike cron,
which is for recurring tasks, &lt;code&gt;at&lt;/code&gt; runs something once and
then the job is gone.&lt;/p&gt;
&lt;div class="sourceCode" id="cb1"&gt;&lt;pre
class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Schedule a command for 10am&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;echo&lt;/span&gt; &lt;span class="st"&gt;&amp;#39;toot post &amp;quot;Hello from the future&amp;quot;&amp;#39;&lt;/span&gt;  &lt;span class="kw"&gt;|&lt;/span&gt; &lt;span class="ex"&gt;at&lt;/span&gt; 10:00&lt;/span&gt;
&lt;span id="cb1-3"&gt;&lt;a href="#cb1-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb1-4"&gt;&lt;a href="#cb1-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# List pending jobs&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-5"&gt;&lt;a href="#cb1-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;atq&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-6"&gt;&lt;a href="#cb1-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb1-7"&gt;&lt;a href="#cb1-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# See what a job will run&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-8"&gt;&lt;a href="#cb1-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;at&lt;/span&gt; &lt;span class="at"&gt;-c&lt;/span&gt; 1&lt;/span&gt;
&lt;span id="cb1-9"&gt;&lt;a href="#cb1-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb1-10"&gt;&lt;a href="#cb1-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Cancel a job&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-11"&gt;&lt;a href="#cb1-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;atrm&lt;/span&gt; 1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;On macOS, you need to enable &lt;code&gt;atrun&lt;/code&gt; first:&lt;/p&gt;
&lt;div class="sourceCode" id="cb2"&gt;&lt;pre
class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;sudo&lt;/span&gt; launchctl load &lt;span class="at"&gt;-w&lt;/span&gt; /System/Library/LaunchDaemons/com.apple.atrun.plist&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s it. No config files, no services to manage. Just
&lt;code&gt;echo "command" | at TIME&lt;/code&gt; and walk away.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Introducing pty</title>
      <link>https://nathanherald.com/posts/introducing-pty/</link>
      <pubDate>Wed, 25 Mar 2026 00:04:20 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/introducing-pty/</guid>
      <description>&lt;p&gt;I’ve been using &lt;a href="https://zellij.dev/"&gt;zellij&lt;/a&gt; full time
for a while and while I liked it better than tmux, I kept running into
weird issues. I couldn’t scroll when using the &lt;a
href="https://github.com/openai/codex"&gt;codex CLI&lt;/a&gt;. It would use 300%
CPU on my Mac while doing almost nothing. Loading sessions from my
iPhone over SSH didn’t render well.&lt;/p&gt;
&lt;p&gt;I use &lt;a href="https://sw.kovidgoyal.net/kitty/"&gt;kitty&lt;/a&gt; which
already supports &lt;a
href="https://sw.kovidgoyal.net/kitty/layouts/"&gt;splits, tabs, and
layouts&lt;/a&gt;, so all I really need from a multiplexer is the ability to
detach from long running processes and come back to them later.&lt;/p&gt;
&lt;p&gt;You don’t need tmux, zellij, or any of those other problematic
multiplexers. You just need pty.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/myobie/pty"&gt;pty&lt;/a&gt; uses &lt;a
href="https://github.com/xtermjs/xterm.js/tree/master/headless"&gt;@xterm/headless&lt;/a&gt;
to run processes inside a real terminal emulator with a real width,
height, full interactivity. Run vim, hx, mactop, dev servers, claude
code, codex, whatever. Detach, it keeps running. Attach, you are back in
it. Attach from multiple clients at once, perfectly fine. Type on one
client, shows up on every attached client.&lt;/p&gt;
&lt;p&gt;I took it further: pty resizes to the smallest currently connected
client, so my phone never tries to render a shell that is meant for my
XDR display.&lt;/p&gt;
&lt;p&gt;I added a &lt;code&gt;peek&lt;/code&gt; command where you can spy on a running
process without fully attaching, just get a snapshot (ANSI or plain
text). And I made it possible to send text or key combos into a
background shell from the outside without attaching.&lt;/p&gt;
&lt;p&gt;And then I realized: if we can remotely spy on and drive a real xterm
session, then we could make a playwright-like terminal testing
framework. And so &lt;a
href="https://github.com/myobie/pty/blob/main/docs/testing.md"&gt;pty has a
testing library&lt;/a&gt;. Test your CLI or TUI with vitest. Assert on the
real output your users see.&lt;/p&gt;
&lt;p&gt;I’m excited to share this first version of pty and I’m looking for
feedback. Hit me up on &lt;a
href="https://www.threads.net/@myobie"&gt;threads&lt;/a&gt; or &lt;a
href="https://indieweb.social/@myobie"&gt;mastodon&lt;/a&gt;.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Kanemaki Yoshitoshi’s Sixteen-Faced Woman</title>
      <link>https://nathanherald.com/posts/links/kanemaki-sculpture/</link>
      <pubDate>Mon, 23 Mar 2026 13:28:57 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/kanemaki-sculpture/</guid>
      <description>&lt;figure&gt;
&lt;a href="https://www.youtube.com/watch?v=74jtS3zrEPU"&gt;&lt;img src="screenshot.jpg" alt="A wooden sculpture of a woman’s face that splits into sixteen overlapping faces when rotated"&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Mesmerizing.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Casio S100X Japanese Lacquer Calculator</title>
      <link>https://nathanherald.com/posts/links/casio-s100x/</link>
      <pubDate>Mon, 23 Mar 2026 12:33:10 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/casio-s100x/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/YTwESBcgoyQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;I hate how much I want this calculator 😆&lt;/p&gt;</description>
    </item>

    <item>
      <title>Christie’s Rostrum</title>
      <link>https://nathanherald.com/posts/links/christies-rostrum/</link>
      <pubDate>Thu, 19 Mar 2026 11:29:50 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/christies-rostrum/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/HLXDxx06_EM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Jony Ive redesigned the Christie’s auction rostrum. The video is
almost cringe, and yet it actually is a good podium and a good
video.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Wave Interference</title>
      <link>https://nathanherald.com/posts/wave-interference/</link>
      <pubDate>Wed, 18 Mar 2026 20:38:24 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/wave-interference/</guid>
      <description>&lt;p&gt;Click or tap anywhere on the canvas to drop a wave. Drop a few and
watch them interact. The colors show where waves add together (bright)
and where they cancel out (dark).&lt;/p&gt;
&lt;p&gt;&lt;nh-include src="canvas.html"&gt;&lt;/nh-include&gt;&lt;/p&gt;</description>
    </item>

    <item>
      <title>Florentine Floor</title>
      <link>https://nathanherald.com/posts/florentine-floor/</link>
      <pubDate>Mon, 16 Mar 2026 15:58:56 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/florentine-floor/</guid>
      <description>&lt;p&gt;Back in 2015 I visited &lt;a
href="https://en.wikipedia.org/wiki/San_Miniato_al_Monte"&gt;San Miniato al
Monte&lt;/a&gt; in Florence. The church floor is covered in memorial slabs,
many of them so worn you can barely read the names and dates anymore,
but the inlaid symbols are still bold and sharp.&lt;/p&gt;
&lt;p&gt;I immediately recognized this cross:&lt;/p&gt;
&lt;figure&gt;
&lt;img src="coudal.jpg" alt="A cross inside a circle, inlaid in marble on the floor of San Miniato al Monte"&gt;
&lt;/figure&gt;

&lt;p&gt;That's the &lt;a
href="https://web.archive.org/web/20210205042901/http://www.coudal.com/about.php"&gt;Coudal
Partners&lt;/a&gt; logo. I knew it from watching way too much &lt;a
href="https://web.archive.org/web/20130116073413/http://www.photoshoptennis.com/"&gt;Photoshop
Tennis&lt;/a&gt; back in the day.&lt;/p&gt;
&lt;p&gt;I kept walking and found this shield:&lt;/p&gt;
&lt;figure&gt;
&lt;img src="shield.jpg" alt="A shield with a cross-hatch pattern, inlaid in marble on the floor of San Miniato al Monte"&gt;
&lt;/figure&gt;

&lt;p&gt;I loved it. Something about it felt right, like it could be my
logo.&lt;/p&gt;
&lt;p&gt;When I got back home to Berlin I looked up the Coudal Partners story
and I couldn't believe it: &lt;a
href="https://web.archive.org/web/20210205042901/http://www.coudal.com/about.php"&gt;Jim
Coudal had found his logo in that same church, on a vacation
himself&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I've tried to trace that shield a few times over the years, but it's
a complex set of shapes and I never got it to a point where I was happy
with it. Recently I decided to try one of those AI services that can
draw SVGs from images, &lt;a href="https://quiver.ai/"&gt;Quiver&lt;/a&gt;, and it
nailed it from my photo.&lt;/p&gt;
&lt;p&gt;I asked claude code to add the logo to my homepage and to the footer
of every page and now I couldn't be happier. Finally I got my shield on
my site.&lt;/p&gt;</description>
    </item>

    <item>
      <title>OlmoTrace</title>
      <link>https://nathanherald.com/posts/links/olmotrace/</link>
      <pubDate>Sun, 15 Mar 2026 13:09:40 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/olmotrace/</guid>
      <description>&lt;p&gt;&lt;a href="https://allenai.org"&gt;Allen AI&lt;/a&gt; is a nonprofit that
publishes everything: model weights, training data, training code,
checkpoints. Their OlmoTrace tool lets you find verbatim matches between
a model's output and its training corpus. You can actually see where the
words came from. You can't do that with Claude, GPT, or even Qwen.&lt;/p&gt;
&lt;p&gt;Their &lt;a href="https://allenai.org/olmo"&gt;Olmo model&lt;/a&gt; is the vegan
impossible burger of AI models. Olmo isn't as capable as Qwen yet (their
own coding agent &lt;a
href="https://allenai.org/blog/open-coding-agents"&gt;SERA&lt;/a&gt; is built on
a Qwen backbone, which tells you where things stand). It's fully
transparent and fully traceable.&lt;/p&gt;
&lt;p&gt;As I work toward owning and operating my own personal, private, local
coding agent, I'll keep an eye on Olmo. Provenance matters.&lt;/p&gt;</description>
    </item>

    <item>
      <title>The Killer | Official Trailer</title>
      <link>https://nathanherald.com/posts/links/the-killer-trailer/</link>
      <pubDate>Sat, 14 Mar 2026 18:06:19 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/the-killer-trailer/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/bdLwJ2-zxDA" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;👀&lt;/p&gt;</description>
    </item>

    <item>
      <title>Jargalant Altai (Special Version)</title>
      <link>https://nathanherald.com/posts/links/jargalant-altai/</link>
      <pubDate>Sat, 14 Mar 2026 18:04:24 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/jargalant-altai/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/sU4Q2IRIlow" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;I've never heard anything quite like this before and it is
awesome.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Generative AI Vegetarianism</title>
      <link>https://nathanherald.com/posts/links/generative-ai-vegetarianism/</link>
      <pubDate>Fri, 13 Mar 2026 10:45:41 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/generative-ai-vegetarianism/</guid>
      <description>&lt;p&gt;Some people are going to choose to be AI vegans and that is cool. No
shame for "eating AI meat" either. See also &lt;a
href="https://simonwillison.net/2022/Aug/29/stable-diffusion/#ai-vegan"&gt;Simon
Willison's post where he first described the concept&lt;/a&gt;. Via &lt;a
href="https://adactio.com/links/22451"&gt;adactio&lt;/a&gt;.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Gaining Agency</title>
      <link>https://nathanherald.com/posts/coding-agents-and-agency/</link>
      <pubDate>Thu, 12 Mar 2026 12:12:36 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/coding-agents-and-agency/</guid>
      <description>&lt;p&gt;When I was at Virginia Tech I taught web design classes through the
YMCA using the on-campus computer labs. I loved making websites and I
wanted to give the superpower of publishing online to as many people as
possible. In one class the head librarian of the town library was there.
I'll never forget when she came in to one class so excited: &lt;strong&gt;she
had updated the library website herself, without asking for help,
without asking permission.&lt;/strong&gt; She felt so empowered.&lt;/p&gt;
&lt;p&gt;Years later I taught web design and development at a professional
school in Washington DC and saw the same thing happen again and again:
that excitement that comes from self-publishing, self-expression,
without guardrails or needing permission or help.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I want as many people as possible to enjoy the web, not be
oppressed by it, not be stuck in algorithmic hell holes.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the late 90s (high school) we were hacking on our computers,
changing everything we could. We (me and my friends) were stealing Adobe
and Macromedia software to make stuff (we eventually bought stuff when
we could afford it). We were ripping and sharing music online, and in
the 2000s even ripping and sharing DVDs with each other. We were making
weird websites, weird online spaces, just terrible code and design, and
we loved it. There was this energy: this is MY computer and I will use
it how I want. My data, my media.&lt;/p&gt;
&lt;p&gt;While it may seem people are stuck on these huge centralized sites
today: there are &lt;a
href="https://bloggingwizard.com/blogging-statistics/"&gt;more blogs now
than ever&lt;/a&gt;. People are still publishing, building their own corners
of the internet, and making stuff.&lt;/p&gt;
&lt;p&gt;Even still, a personal website is tough to manage for most people and
many people can only publish in the ways their CMS allows.&lt;/p&gt;
&lt;p&gt;Coding agents &lt;em&gt;can&lt;/em&gt; expand agency over one's online presence
and data. They might be the first time &lt;em&gt;"use a computer to do what
you want"&lt;/em&gt; is actually accessible to everyone, not just people
willing to learn a very technical system. Every previous wave of web
tools, no matter how simple, still required you to learn a complex tool.
Agents let you describe what you want and get something tailor made.
That's a fundamentally different relationship with technology.&lt;/p&gt;
&lt;p&gt;I &lt;a href="/posts/no-cms/"&gt;removed the CMS from my website&lt;/a&gt; as an
experiment and now I just tell claude code what to post. I write the
text, it does everything else (it published this post, for example). And
one of my goals this year is to own all my data and eventually own my
inference (my own coding agent model), running local open source models
instead of relying on any company.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Would you be interested in having a website setup like mine,
where you can ask to post or make a change and it just happens?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One of the most valuable things about a coding agent for me is
getting past blockers: something that would take too much time,
something tedious, or something just beyond my knowledge by a tiny
bit.&lt;/p&gt;
&lt;p&gt;For example: &lt;strong&gt;HTML is way easier to read than to
write.&lt;/strong&gt; People still need to learn new things to publish online,
but now there is an on-ramp. The agent can explain it, write the first
draft, and when you would have gotten stuck in the past the agent can
help you get over the hump.&lt;/p&gt;
&lt;p&gt;When I was leading the web design department of that professional
school I built my own CRM to manage incoming / outgoing emails. People
would come in and teach certain modules that I needed to keep up with, I
needed to know when I last emailed them, produce reports of who I hadn't
emailed recently, etc. I was able to build that and was way more
productive than my peers because I could code. And some of my peers
today think that is alright: I deserve to be more productive because I
know how to code. I disagree.&lt;/p&gt;
&lt;p&gt;Now, someone could probably cobble together something similar with a
coding agent without needing to have put in the time I have. This is a
great thing. My goal is always more people doing more with the tech and
data they own, not to gate keep or hoard.&lt;/p&gt;
&lt;p&gt;The web is what we make of it. We have a great interconnected world
today, with a few too many centralized big players. We can make more
spaces for individuals and small groups. I think agents can help.
Especially if we create open, locally running agents.&lt;/p&gt;
&lt;p&gt;Should I make some online courses about self-publishing online with a
personal coding agent? 🤔 Let me know what you think &lt;a
href="https://indieweb.social/@myobie"&gt;on mastodon&lt;/a&gt;.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Gugusse et l'Automate</title>
      <link>https://nathanherald.com/posts/links/gugusse-et-lautomate/</link>
      <pubDate>Wed, 11 Mar 2026 22:14:41 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/gugusse-et-lautomate/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/ojqcjzzjN2Q" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;This might be the very first film with a robot, and probably the
first sci fi film.&lt;/p&gt;</description>
    </item>

    <item>
      <title>dandelions forever</title>
      <link>https://nathanherald.com/posts/links/dandelions-forever/</link>
      <pubDate>Wed, 11 Mar 2026 10:43:14 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/dandelions-forever/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/RUsUQ9tRHHQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;2000s dreamcore vibes.&lt;/p&gt;</description>
    </item>

    <item>
      <title>csesh – monitoring and reconnecting Claude Code sessions</title>
      <link>https://nathanherald.com/posts/csesh/</link>
      <pubDate>Tue, 10 Mar 2026 15:36:22 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/csesh/</guid>
      <description>&lt;p&gt;I &lt;a href="/posts/no-cms/"&gt;removed the CMS from my website&lt;/a&gt; and
now I just ask Claude Code to make new posts for me. I write the text,
Claude puts a markdown file in the right place, runs some tooling it
made for itself, and boom it's on my website.&lt;/p&gt;
&lt;p&gt;I want to take this further with &lt;a
href="https://code.claude.com/docs/en/remote-control"&gt;Claude Code Remote
Control&lt;/a&gt; so I can make a post from my phone anytime by asking the
Claude Code on my home Mac to post for me. But it keeps
disconnecting.&lt;/p&gt;
&lt;p&gt;So I made &lt;a href="https://github.com/myobie/csesh"&gt;csesh&lt;/a&gt;, a set
of bash utilities for monitoring Claude Code sessions and forcing them
to reconnect. It builds on my &lt;a href="/posts/pty/"&gt;pty wrapper&lt;/a&gt;,
since I can not only detach from a process, I can send text to the
backgrounded pty from the outside. A script can peek at the pty, send it
&lt;code&gt;/reconnect&amp;lt;return&amp;gt;&lt;/code&gt; to trigger a reconnect, and peek
again to see if it worked.&lt;/p&gt;
&lt;p&gt;The tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;csesh&lt;/strong&gt; lists running Claude Code sessions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;creconnect&lt;/strong&gt; reconnects disconnected sessions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ckeepalive&lt;/strong&gt; automatically reconnects every 5
minutes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is my goal this year to make sure I own all my data and eventually
own my inference, so I can use a local, open source model instead of
Claude for this website. One step at a time.&lt;/p&gt;</description>
    </item>

    
    <item>
      <title>TIL Acorn can screenshot with layers</title>
      <link>https://nathanherald.com/posts/til/acorn-screenshot-layers/</link>
      <pubDate>Thu, 05 Mar 2026 11:09:31 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/til/acorn-screenshot-layers/</guid>
      <description>&lt;p&gt;TIL that &lt;a href="https://flyingmeat.com/acorn/"&gt;Acorn&lt;/a&gt; can take a
screenshot and every window is on its own layer! You can move them
around, hide some, etc. How cool.&lt;/p&gt;</description>
    </item>

    <item>
      <title>toe – Distance Sessions 2020</title>
      <link>https://nathanherald.com/posts/links/toe-distance-sessions-2020/</link>
      <pubDate>Wed, 04 Mar 2026 17:09:34 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/toe-distance-sessions-2020/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/jDHmg8Mb9wo" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;One of my favorite shoegaze music videos. Hoping to see toe in Berlin
later this year 👟&lt;/p&gt;</description>
    </item>

    <item>
      <title>Hazel Dickens: They'll Never Keep Us Down and Which Side Are You On?</title>
      <link>https://nathanherald.com/posts/links/hazel-dickens-theyll-never-keep-us-down/</link>
      <pubDate>Mon, 02 Mar 2026 11:04:38 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/hazel-dickens-theyll-never-keep-us-down/</guid>
      <description>&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/wnGCVSWrqs0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;If someone were to ask me what my politics were, I'd probably point
them to this video to start.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Leica T Leather Holster</title>
      <link>https://nathanherald.com/posts/links/leica-t-leather-holster/</link>
      <pubDate>Thu, 26 Feb 2026 15:42:10 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/leica-t-leather-holster/</guid>
      <description>&lt;p&gt;I've wanted this holster for a while and finally found one on
Kleinanzeigen. It's a simple thing, a leather pouch that clips to your
belt, but it makes such a difference. I actually have my camera with me
now instead of leaving it at home because I don't want to deal with a
bag.&lt;/p&gt;</description>
    </item>

    <item>
      <title>CRDT File Sync</title>
      <link>https://nathanherald.com/posts/links/crdt-filesync/</link>
      <pubDate>Wed, 25 Feb 2026 13:08:53 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/crdt-filesync/</guid>
      <description>&lt;p&gt;I hadn't ever thought of having each device write its own database /
file to a synced folder, and then just periodically having each device
read all the other files to accumulate a fully synced state. It's such a
simple, clever idea: no conflicts at the file-sync layer because each
client only writes to its own file, and CRDTs handle merging the actual
data.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Claude Code Remote Control</title>
      <link>https://nathanherald.com/posts/links/claude-code-remote-control/</link>
      <pubDate>Wed, 25 Feb 2026 11:51:49 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/links/claude-code-remote-control/</guid>
      <description>&lt;p&gt;Anthropic just dropped Remote Control for Claude Code, letting you
remote control your computer from your phone. Claude keeps running
locally, your phone is just a window into the session.&lt;/p&gt;
&lt;p&gt;I drafted this post from my phone using Remote Control and published
it from my computer. It's a nice workflow: my Mac keeps working and I
can message it from anywhere.&lt;/p&gt;</description>
    </item>

    <item>
      <title>The 4 things that make an "agent" today</title>
      <link>https://nathanherald.com/posts/the-4-things-that-make-an-agent/</link>
      <pubDate>Tue, 24 Feb 2026 15:53:29 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/the-4-things-that-make-an-agent/</guid>
      <description>&lt;h1 id="the-4-things-that-make-an-agent-today"&gt;The 4 things that make an
"agent" today&lt;/h1&gt;
&lt;p&gt;Six months ago, we agreed on a definition for an "agent:" &lt;a
href="https://simonwillison.net/2025/Sep/18/agents/"&gt;an LLM with tools
in a loop&lt;/a&gt; &lt;a
href="https://simonwillison.net/2025/Sep/30/designing-agentic-loops/"&gt;iterating
toward a goal&lt;/a&gt;. But recently the definition has expanded. While the
entire industry has slowly been figuring out what "an agent" is by
trying different things, &lt;a href="https://openclaw.ai/"&gt;OpenClaw&lt;/a&gt;
certainly has captured a lot of people's imaginations and helped
crystallize what people &lt;em&gt;think&lt;/em&gt; an agent &lt;em&gt;should&lt;/em&gt; be able
to do.&lt;/p&gt;
&lt;h2 id="an-agent-today-is-4-things"&gt;An agent today is 4 things&lt;/h2&gt;
&lt;ol type="1"&gt;
&lt;li&gt;Language model with tools in a loop&lt;/li&gt;
&lt;li&gt;Scheduled tasks&lt;/li&gt;
&lt;li&gt;Memory or session persistence&lt;/li&gt;
&lt;li&gt;Skills&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This has become the standard packaging for an agent over the last 6
months. This is basically how OpenClaw describes itself, &lt;a
href="https://www.notion.com/help/guides/get-started-with-your-personal-agent-in-notion"&gt;Notion's
AI agent&lt;/a&gt; has roughly the same ideas, and even &lt;a
href="https://claude.ai/"&gt;Claude&lt;/a&gt; and other products from frontier
labs are describing themselves in similar ways.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Language models with tools in a loop&lt;/strong&gt; is the
foundation everything else is built on. The model &lt;em&gt;reasons&lt;/em&gt; about
a problem, calls a tool, sees the result, and decides what to do next
until it reaches its goal or gets stuck. This is the core mechanic, the
&lt;em&gt;engine.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scheduled tasks:&lt;/strong&gt; everyone seems to always ask agents
to "remind me to do this tomorrow", "every Monday and Thursday morning
prepare and show me a report about…", "watch this web page and tell me
when the price drops…", etc. So this has become table stakes for an
agent: can it queue things up for later or to happen regularly? OpenClaw
has the &lt;a
href="https://docs.openclaw.ai/gateway/heartbeat"&gt;&lt;code&gt;HEARTBEAT.md&lt;/code&gt;&lt;/a&gt;,
but a sqlite database would work fine as well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Memory or session persistence:&lt;/strong&gt; the agent needs to
not forget everything all the time. Coding agents already have this: you
can resume work from the past to jump right back where you were. General
agents need this as well.&lt;/p&gt;
&lt;p&gt;Simon Willison &lt;a
href="https://simonwillison.net/2025/Sep/18/agents/"&gt;has argued&lt;/a&gt; that
memory is just "more tools," that you can bolt long-term memory onto the
basic tools in a loop definition with a read/write memory tool. I think
that undersells it.&lt;/p&gt;
&lt;p&gt;Long term session persistence is a hard problem because language
models have limited &lt;a
href="https://www.ibm.com/think/topics/context-window"&gt;"context
windows"&lt;/a&gt;, so coding agents today implement lossy &lt;em&gt;compaction&lt;/em&gt;
which isn't great. Everyone is looking for better ways to
&lt;em&gt;remember&lt;/em&gt; right now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skills&lt;/strong&gt; are what really captures people's
imaginations: &lt;strong&gt;agents can do things on your behalf.&lt;/strong&gt;
There are even "skill files" that are &lt;a
href="https://github.com/skillmatic-ai/awesome-agent-skills"&gt;almost a
standard&lt;/a&gt; at this point.&lt;/p&gt;
&lt;p&gt;The most useful skills are the authenticated ones: like access to
your calendar, email, messages, notes, etc. These are also the scariest,
the agent could &lt;a
href="https://www.threads.com/@firerock31/post/DVHGBX2Epgq?xmt=AQF0a-tPvV_-XenNgad1ks8gRHKKJnD-NzAhV2CEDbaVfA"&gt;run
away with or delete your emails&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="a-note-on-security"&gt;A note on security&lt;/h3&gt;
&lt;p&gt;Unless you know how to build secure systems, don't run something like
OpenClaw with your real email credentials or trust some SaaS to run it
for you. Don't let anyone tell you your credentials are encrypted or
that their system is 100% secure or any other nonsense. Nothing is 100%
secure, the goals are always:&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;Minimize risk&lt;/li&gt;
&lt;li&gt;Minimize blast radius&lt;/li&gt;
&lt;li&gt;Increase your confidence in the system&lt;/li&gt;
&lt;li&gt;Not be too annoying (or you might turn off the security
features)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Doing those things iteratively over time is the only way to actually
produce a secure system of any kind.&lt;/p&gt;
&lt;p&gt;Also: most best practices for security assume the code we write and
execute is trustworthy and we are protecting ourselves from someone
gaining access to our systems or seeing our credentials fly by. However,
&lt;strong&gt;now the program itself is both dangerous and untrustworthy and
can execute arbitrary code.&lt;/strong&gt; So if you are not an expert, please
know any advice you read might just be coming at this from the wrong
angle in the first place.&lt;/p&gt;
&lt;p&gt;The agent cannot be trusted with your real credentials or to execute
code outside of any sandbox. There must be clear boundaries.&lt;/p&gt;
&lt;p&gt;It doesn't matter if your credentials are encrypted at rest if the
agent can see them in plain text while it is calling the gmail api to
delete all your emails, or worse it starts forwarding your emails to
some rando.&lt;/p&gt;
&lt;p&gt;I believe any system can be made "secure" for an acceptable
definition of "secure," even an agent. And while &lt;a
href="https://github.com/dedene/claw-wrap"&gt;there are projects to try to
wrap OpenClaw,&lt;/a&gt; I am suspicious if it can ever be fully "wrapped" up
like that. It really needed to have been built with security and privacy
in mind from the start to really get to a place where I would feel
confident in it.&lt;/p&gt;
&lt;h2 id="coding-agents-on-your-own-hardware-are-best"&gt;Coding agents on
your own hardware are best&lt;/h2&gt;
&lt;p&gt;I have two personal opinions that I will layer on top:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. The best agent is a coding agent and 2. the best place to
run an agent is on your own hardware.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;All agents will be coding agents soon, I predict.&lt;/strong&gt; A
coding agent can build custom solutions to get to a goal, it can create
new skills on-demand, and the programs it writes can be audited, tested,
and easily modified. Claude (the non-code one) will already write and
execute python code on demand to do things like make word and excel
documents… so it already is a coding agent some of the time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Writing executable code that statistically is very close
to correct is way more useful and valuable than writing prose that is
statistically very close to correct.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For privacy and security, your agent(s) should run on your hardware.
SaaS doesn't seem like a good fit for highly personal agents that can
take actions on your behalf. &lt;a
href="https://www.tomshardware.com/tech-industry/artificial-intelligence/openclaw-fueled-ordering-frenzy-creates-apple-mac-shortage-delivery-for-high-unified-memory-units-now-ranges-from-6-days-to-6-weeks"&gt;This
is why Mac minis have been flying off the shelves.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Your data lives on your device (or in some cloud you chose that your
device is connected to) and you shouldn't need to transmit all of that
up to someone else's servers just to tally up how my workouts have been
going recently… or whatever it is I want an agent to do. Right now it is
difficult to directly run a good model locally, so a &lt;em&gt;local
agent&lt;/em&gt; might still use Claude or ChatGPT for inference.&lt;/p&gt;
&lt;p&gt;For local inference, &lt;a href="https://ollama.com/"&gt;ollama&lt;/a&gt; lets
you run open-weights models on your own machine. &lt;a
href="https://ollama.com/library/qwen3-coder-next"&gt;&lt;code&gt;qwen3-coder-next&lt;/code&gt;&lt;/a&gt;
is a very impressive model for being so small (80B, 85GB at 8-bit) — it
very well could power a sophisticated agent today on a high-memory Mac
Mini or similar hardware.&lt;/p&gt;
&lt;p&gt;We've been working hard &lt;a href="https://new.space/"&gt;at my day
job&lt;/a&gt; making it easy to run AI in a private space, and we want to show
what a safe, private, capable agent can look like. &lt;a
href="https://shareup.world/"&gt;Follow our newsletter&lt;/a&gt; for new
capabilities coming very soon.&lt;/p&gt;
&lt;h2 id="where-this-is-heading"&gt;Where this is heading&lt;/h2&gt;
&lt;p&gt;OpenClaw showed people what's possible, even if &lt;a
href="https://www.crowdstrike.com/en-us/blog/what-security-teams-need-to-know-about-openclaw-ai-super-agent/"&gt;the
security story is pretty scary for it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;pattern&lt;/em&gt; is what matters, not any single project. Tools
in a loop, scheduled tasks, memory, and skills. This is the
architecture. Better implementations are coming, ones that are built
with security from the ground up, that run on your own hardware, and
that treat the agent as untrusted code by default.&lt;/p&gt;
&lt;h2 id="what-is-your-use-case-for-an-agent"&gt;What is your use case for an
agent?&lt;/h2&gt;
&lt;p&gt;What would you use one of these autonomous agents for? What does your
dream agent look like?&lt;/p&gt;
&lt;p&gt;Hit me up on mastodon or threads, I'd love to hear what you
think.&lt;/p&gt;</description>
    </item>

    <item>
      <title>Two interesting AI developments</title>
      <link>https://nathanherald.com/posts/two-interesting-ai-developments/</link>
      <pubDate>Mon, 09 Feb 2026 18:34:01 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/two-interesting-ai-developments/</guid>
      <description>&lt;p&gt;Two things I saw recently that prompted me to &lt;a href="/ai"&gt;update my
/ai page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://openclaw.ai/"&gt;OpenClaw&lt;/a&gt;&lt;/strong&gt; is an
open-source, self-hosted personal AI agent by Peter Steinberger
(steipete, founder of PSPDFKit). It's an AI that doesn't just chat but
actually does things: it can message people via WhatsApp, Telegram, and
Slack, control a browser, automate your devices, and has an extensible
plugin system. It runs on your own hardware. The project has had quite
the naming saga -- it started as Clawdis, became Clawdbot (~30k GitHub
stars), got renamed to Moltbot after Anthropic's legal team reached out
about the name, and finally landed on OpenClaw. It now has 179k+ GitHub
stars. It's a huge project and worth keeping an eye on, though it does
have all three legs of Simon Willison's &lt;a
href="https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/"&gt;lethal
trifecta&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a
href="https://mitchellh.com/writing/my-ai-adoption-journey"&gt;My AI
Adoption Journey&lt;/a&gt;&lt;/strong&gt; by Mitchell Hashimoto (co-founder of
HashiCorp) documents his six-phase path from AI skepticism to running
agents continuously. The whole thing is worth reading. I've had a
similar journey, especially the part where he forces himself to
reproduce his own work with the agent. I took the time to do that too:
sit down and try to get the coding agent to write the code how I would
write it. Now I feel more confident being able to instruct the robot to
do things and know that the output will be pretty reasonable. That
confidence doesn't come from the tools getting better (though they are).
It comes from me learning what they need to hear.&lt;/p&gt;</description>
    </item>

    <item>
      <title>You Could Configure Your App with Markdown</title>
      <link>https://nathanherald.com/posts/configure-app-with-markdown/</link>
      <pubDate>Wed, 28 Jan 2026 12:42:19 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/configure-app-with-markdown/</guid>
      <description>&lt;p&gt;I keep finding little uses for small local SLMs in my side projects,
and one that I really like is using Apple's Foundation Model to parse a
&lt;code&gt;config.md&lt;/code&gt; file into an &lt;code&gt;AppConfig&lt;/code&gt; struct. You
could configure your display name, your location, what time you'd like
to receive notifications/reminders, the app's theme, and more in plain
language.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SLM == "Small Language Model" which is what I assume Apple's
on-device model is. LLMs are the giant ones that run on servers or beefy
machines.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Apple's Foundation Models are different from all other SLMs because
they are more directly integrated into swift. I don't have to ask the
robot to produce json, parse that, see if it is valid, etc. They handle
all of that behind the scenes and I receive a valid swift struct or
enum.&lt;/p&gt;
&lt;h2 id="why-markdown-instead-of-json-or-a-settings-ui"&gt;Why markdown
instead of json or a settings UI?&lt;/h2&gt;
&lt;p&gt;I'm building a small journal app for myself that's all markdown.
Having the config also be a markdown file keeps the experience
consistent for me. It's all just text files I can edit however I
want.&lt;/p&gt;
&lt;p&gt;Beyond that, there are real advantages to letting an SLM parse
natural language config. Natural language can make setting a time in
your app way easier since "9am" and "9:00 AM" and "nine in the morning"
and "early, like 9ish" can all work. And there is less UI to build:
instead of designing screens with toggles, dropdowns, and date pickers,
you just need a text editor. Plus users can even include notes about why
they're choosing to configure something a certain way right there in the
markdown.&lt;/p&gt;
&lt;p&gt;This pattern works for other things too: natural language search
("show me photos from last summer"), extracting data from messy text
(receipts, emails), smart tagging, or anywhere users would benefit from
not having to learn your app's specific input format.&lt;/p&gt;
&lt;p&gt;Here is a little app I made. You can find the project up on &lt;a
href="https://github.com/myobie/ConfigWithMarkdown"&gt;GitHub&lt;/a&gt;. It's
just a one file SwiftUI macOS app.&lt;/p&gt;
&lt;p&gt;&lt;img src="screenshot.png"
alt="Screenshot of ConfigWithMarkdown app showing a two-pane layout: markdown config on the left, parsed struct values on the right" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I can't provide a Playground because playgrounds cannot use macros,
so they can't show off the &lt;code&gt;@Generable&lt;/code&gt; stuff. So it must be
a whole ass xcode project. Sorry.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My dream config struct for this app looks like this:&lt;/p&gt;
&lt;div class="sourceCode" id="cb1"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;@Generable&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;enum&lt;/span&gt; Theme&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;String&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-3"&gt;&lt;a href="#cb1-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="cf"&gt;case&lt;/span&gt; light&lt;/span&gt;
&lt;span id="cb1-4"&gt;&lt;a href="#cb1-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="cf"&gt;case&lt;/span&gt; dark&lt;/span&gt;
&lt;span id="cb1-5"&gt;&lt;a href="#cb1-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-6"&gt;&lt;a href="#cb1-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb1-7"&gt;&lt;a href="#cb1-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;@Generable&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-8"&gt;&lt;a href="#cb1-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;struct&lt;/span&gt; AppConfig&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;Equatable&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-9"&gt;&lt;a href="#cb1-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="at"&gt;@Guide&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;description&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;The user&amp;#39;s display name if specified&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-10"&gt;&lt;a href="#cb1-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;name&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; String&lt;/span&gt;
&lt;span id="cb1-11"&gt;&lt;a href="#cb1-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb1-12"&gt;&lt;a href="#cb1-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="at"&gt;@Guide&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;description&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;The user&amp;#39;s preferred color theme. Use &amp;#39;light&amp;#39; for bright backgrounds and dark text, good for daytime or well-lit environments. Use &amp;#39;dark&amp;#39; for dark backgrounds and light text, easier on the eyes at night or in dim rooms.&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-13"&gt;&lt;a href="#cb1-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;theme&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; Theme&lt;/span&gt;
&lt;span id="cb1-14"&gt;&lt;a href="#cb1-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb1-15"&gt;&lt;a href="#cb1-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="at"&gt;@Guide&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;description&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Notification hour in 24-hour format (0-23), or -1 if not specified. Pay attention to AM vs PM and daytime vs evening context.&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-16"&gt;&lt;a href="#cb1-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;notificationHour&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; Int&lt;/span&gt;
&lt;span id="cb1-17"&gt;&lt;a href="#cb1-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;(I wanted to mix in an enum just to show how fancy this can
be.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Guide&lt;/code&gt; is for the SLM to try to help it figure out what
these fields are for. This is "prompt engineering" right here. You can
see I've tried to &lt;em&gt;explain&lt;/em&gt; a bit why one might want light vs
dark mode, which hopefully would work for a config file that says
something like "I mainly work at night."&lt;/p&gt;
&lt;p&gt;This is how you could use Apple's local model to parse some markdown
into that struct:&lt;/p&gt;
&lt;div class="sourceCode" id="cb2"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;func&lt;/span&gt; &lt;span class="fu"&gt;parseConfig&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="va"&gt;markdown&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;String&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="fu"&gt;async&lt;/span&gt; &lt;span class="kw"&gt;throws&lt;/span&gt; -&amp;gt; &lt;span class="fu"&gt;AppConfig&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;model&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; SystemLanguageModel&lt;span class="op"&gt;(&lt;/span&gt;guardrails&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="op"&gt;.&lt;/span&gt;permissiveContentTransformations&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;session&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; LanguageModelSession&lt;span class="op"&gt;(&lt;/span&gt;model&lt;span class="op"&gt;:&lt;/span&gt; model&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-4"&gt;&lt;a href="#cb2-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-5"&gt;&lt;a href="#cb2-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;prompt&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-6"&gt;&lt;a href="#cb2-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;    Extract settings from this config file:&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-7"&gt;&lt;a href="#cb2-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-8"&gt;&lt;a href="#cb2-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;    &lt;/span&gt;&lt;span class="er"&gt;\(&lt;/span&gt;&lt;span class="st"&gt;markdown)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-9"&gt;&lt;a href="#cb2-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-10"&gt;&lt;a href="#cb2-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;    Look for the user&amp;#39;s name, theme preference (dark/light mode), and any notification time they&amp;#39;ve specified.&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-11"&gt;&lt;a href="#cb2-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-12"&gt;&lt;a href="#cb2-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-13"&gt;&lt;a href="#cb2-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;response&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="cf"&gt;try&lt;/span&gt; &lt;span class="cf"&gt;await&lt;/span&gt; session&lt;span class="op"&gt;.&lt;/span&gt;respond&lt;span class="op"&gt;(&lt;/span&gt;to&lt;span class="op"&gt;:&lt;/span&gt; prompt&lt;span class="op"&gt;,&lt;/span&gt; generating&lt;span class="op"&gt;:&lt;/span&gt; AppConfig&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="kw"&gt;self&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-14"&gt;&lt;a href="#cb2-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;return&lt;/span&gt; response&lt;span class="op"&gt;.&lt;/span&gt;content&lt;/span&gt;
&lt;span id="cb2-15"&gt;&lt;a href="#cb2-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;OK, about the &lt;code&gt;guardrails&lt;/code&gt;: it's not what you think. The
default guardrails are so restrictive that they won't work a majority of
the time. With the default guardrails if you ask the SLM to "tell me a
joke" it will refuse to do so. And even with
&lt;code&gt;.permissiveContentTransformations&lt;/code&gt; there are still a ton of
guardrails on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IMHO Apple's models are useless without
&lt;code&gt;.permissiveContentTransformations&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The rest of the app is the SwiftUI to make the two pane layout +
button. You can view it all on &lt;a
href="https://github.com/myobie/ConfigWithMarkdown"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And you don't have to use Apple's models. At work we've open sourced
our code we use to boot up and use on-device MLX SLMs like &lt;a
href="https://huggingface.co/mlx-community/Qwen3-VL-4B-Instruct-4bit"&gt;Qwen
3&lt;/a&gt; in our &lt;a href="https://new.space"&gt;new.space&lt;/a&gt; app – our library
is called &lt;a href="https://github.com/shareup/shllm"&gt;SHLLM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can also just use &lt;a href="https://ollama.com"&gt;Ollama&lt;/a&gt; or
another local model manager that exposes an HTTP api and use any OpenAI
compatible HTTP client package.&lt;/p&gt;
&lt;p&gt;And soon small models will be able to solve little problems on-device
even on the web. Chrome is experimenting with a built-in &lt;a
href="https://developer.chrome.com/docs/ai/prompt-api"&gt;Prompt API&lt;/a&gt;
that runs Gemini Nano locally in the browser. I made a &lt;a
href="/chrome-ai-test/"&gt;test page&lt;/a&gt; that parses a markdown config the
same way. It's still in origin trial, so it might not work for you yet,
but it's coming.&lt;/p&gt;
&lt;p&gt;Small models can do lots of small, interesting things like this. I am
continually looking for more places to use little models to solve little
problems. And it's &lt;strong&gt;all on-device,&lt;/strong&gt; nothing leaves or
goes to any company anywhere, &lt;strong&gt;which is important to me.&lt;/strong&gt;
More and more I want more things running on my devices when
possible.&lt;/p&gt;
&lt;p&gt;What ideas do you have for where to use local models? What are some
problems you've used small models to solve yourself? Hit me up on &lt;a
href="https://indieweb.social/@myobie"&gt;mastodon&lt;/a&gt; or &lt;a
href="https://www.threads.com/@myobie"&gt;threads&lt;/a&gt;.&lt;/p&gt;</description>
    </item>

    <item>
      <title>SwiftUI @State Considered Harmful</title>
      <link>https://nathanherald.com/posts/swiftui-state-considered-harmful/</link>
      <pubDate>Tue, 02 Dec 2025 23:32:28 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/swiftui-state-considered-harmful/</guid>
      <description>&lt;h2 id="the-problem-with-state-for-collections"&gt;The Problem with
&lt;code&gt;@State&lt;/code&gt; for Collections&lt;/h2&gt;
&lt;p&gt;When you're building a SwiftUI app with lists of items, like say
podcast episodes, the obvious first approach is &lt;code&gt;@State&lt;/code&gt;:&lt;/p&gt;
&lt;div class="sourceCode" id="cb1"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;struct&lt;/span&gt; EpisodeListView&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;View&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="at"&gt;@State&lt;/span&gt; &lt;span class="kw"&gt;private&lt;/span&gt; &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;episodes&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="op"&gt;[&lt;/span&gt;Episode&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;[]&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-3"&gt;&lt;a href="#cb1-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb1-4"&gt;&lt;a href="#cb1-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;body&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; some View &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-5"&gt;&lt;a href="#cb1-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        List&lt;span class="op"&gt;(&lt;/span&gt;episodes&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt; episode &lt;span class="cf"&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-6"&gt;&lt;a href="#cb1-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            EpisodeRow&lt;span class="op"&gt;(&lt;/span&gt;episode&lt;span class="op"&gt;:&lt;/span&gt; episode&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-7"&gt;&lt;a href="#cb1-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-8"&gt;&lt;a href="#cb1-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-9"&gt;&lt;a href="#cb1-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This seems to work at first. But then I notice UI stuttering while
using the app…&lt;/p&gt;
&lt;p&gt;The moment you change &lt;em&gt;any&lt;/em&gt; property on &lt;em&gt;any&lt;/em&gt; episode,
SwiftUI invalidates the entire &lt;code&gt;@State&lt;/code&gt; array. Every
&lt;code&gt;EpisodeRow&lt;/code&gt; re-renders. With 100 episodes and a playback
position updating every second, you're re-rendering 100 rows every
second. 1,000 episodes and things get real sad. Your UI stutters. Your
battery drains.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fundamental problem:&lt;/strong&gt; &lt;code&gt;@State&lt;/code&gt; treats
the entire array as a single unit of change. SwiftUI can't see
&lt;em&gt;inside&lt;/em&gt; structs to know that only
&lt;code&gt;episodes[42].playbackPosition&lt;/code&gt; changed, for example. It sees
"the array changed" and rebuilds everything.&lt;/p&gt;
&lt;h2
id="the-solution-observable-collections-of-observables-with-identity-preservation"&gt;The
Solution: Observable Collections of Observables with Identity
Preservation&lt;/h2&gt;
&lt;p&gt;SwiftUI seems to have a lot of the downsides of the "just rerender
everything" frameworks like React. Luckily, I have dealt with react,
preact, svelte, etc. &lt;strong&gt;Granular reactivity requires granular
changes and stable identity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After a lot of iteration, this of what I've landed on. I am not a
SwiftUI expert, so please tell me if this is terrible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ive made each item an &lt;code&gt;@Observable&lt;/code&gt; class, stored
them by reference in a dictionary, and I update them
in-place.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="sourceCode" id="cb2"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;@Observable&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;final&lt;/span&gt; &lt;span class="kw"&gt;class&lt;/span&gt; Episode&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;Identifiable&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="at"&gt;@unchecked&lt;/span&gt; Sendable &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;id&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; String&lt;/span&gt;
&lt;span id="cb2-4"&gt;&lt;a href="#cb2-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;title&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; String&lt;/span&gt;
&lt;span id="cb2-5"&gt;&lt;a href="#cb2-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;playbackPosition&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; Int&lt;/span&gt;
&lt;span id="cb2-6"&gt;&lt;a href="#cb2-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;isDownloaded&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; Bool&lt;/span&gt;
&lt;span id="cb2-7"&gt;&lt;a href="#cb2-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="co"&gt;// ... other properties&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-8"&gt;&lt;a href="#cb2-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-9"&gt;&lt;a href="#cb2-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;func&lt;/span&gt; &lt;span class="fu"&gt;update&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="va"&gt;from&lt;/span&gt; &lt;span class="va"&gt;snapshot&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;EpisodeSnapshot&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-10"&gt;&lt;a href="#cb2-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="kw"&gt;self&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;title &lt;span class="op"&gt;=&lt;/span&gt; other&lt;span class="op"&gt;.&lt;/span&gt;title&lt;/span&gt;
&lt;span id="cb2-11"&gt;&lt;a href="#cb2-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="kw"&gt;self&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;playbackPosition &lt;span class="op"&gt;=&lt;/span&gt; other&lt;span class="op"&gt;.&lt;/span&gt;playbackPosition&lt;/span&gt;
&lt;span id="cb2-12"&gt;&lt;a href="#cb2-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="kw"&gt;self&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;isDownloaded &lt;span class="op"&gt;=&lt;/span&gt; other&lt;span class="op"&gt;.&lt;/span&gt;isDownloaded&lt;/span&gt;
&lt;span id="cb2-13"&gt;&lt;a href="#cb2-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="co"&gt;// ... update all properties except identity (id)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-14"&gt;&lt;a href="#cb2-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-15"&gt;&lt;a href="#cb2-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When I build an app I am dogmatic that there should be one "app
state" that exists outside the UI and is granularly reactive by default.
It can be unit tested. And when I look at this object I should be able
to imagine what the UI looks like.&lt;/p&gt;
&lt;div class="sourceCode" id="cb3"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb3-1"&gt;&lt;a href="#cb3-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;@MainActor&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-2"&gt;&lt;a href="#cb3-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;@Observable&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-3"&gt;&lt;a href="#cb3-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;final&lt;/span&gt; &lt;span class="kw"&gt;class&lt;/span&gt; AppState &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-4"&gt;&lt;a href="#cb3-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;episodes&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="op"&gt;[&lt;/span&gt;String&lt;span class="op"&gt;:&lt;/span&gt; Episode&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;[:]&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-5"&gt;&lt;a href="#cb3-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb3-6"&gt;&lt;a href="#cb3-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;func&lt;/span&gt; &lt;span class="fu"&gt;mergeEpisodes&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="va"&gt;_&lt;/span&gt; &lt;span class="va"&gt;incoming&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; [&lt;span class="dt"&gt;String&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;EpisodeSnapshot&lt;/span&gt;]&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-7"&gt;&lt;a href="#cb3-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;incomingIds&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; Set&lt;span class="op"&gt;(&lt;/span&gt;incoming&lt;span class="op"&gt;.&lt;/span&gt;keys&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-8"&gt;&lt;a href="#cb3-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;existingIds&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; Set&lt;span class="op"&gt;(&lt;/span&gt;episodes&lt;span class="op"&gt;.&lt;/span&gt;keys&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-9"&gt;&lt;a href="#cb3-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb3-10"&gt;&lt;a href="#cb3-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="co"&gt;// Remove deleted&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-11"&gt;&lt;a href="#cb3-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="cf"&gt;for&lt;/span&gt; id &lt;span class="cf"&gt;in&lt;/span&gt; existingIds&lt;span class="op"&gt;.&lt;/span&gt;subtracting&lt;span class="op"&gt;(&lt;/span&gt;incomingIds&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-12"&gt;&lt;a href="#cb3-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            episodes&lt;span class="op"&gt;.&lt;/span&gt;removeValue&lt;span class="op"&gt;(&lt;/span&gt;forKey&lt;span class="op"&gt;:&lt;/span&gt; id&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-13"&gt;&lt;a href="#cb3-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-14"&gt;&lt;a href="#cb3-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb3-15"&gt;&lt;a href="#cb3-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="co"&gt;// Update existing or add new&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-16"&gt;&lt;a href="#cb3-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="cf"&gt;for&lt;/span&gt; &lt;span class="op"&gt;(&lt;/span&gt;id&lt;span class="op"&gt;,&lt;/span&gt; incomingEpisode&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="cf"&gt;in&lt;/span&gt; incoming &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-17"&gt;&lt;a href="#cb3-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="cf"&gt;if&lt;/span&gt; &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;existing&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; episodes&lt;span class="op"&gt;[&lt;/span&gt;id&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-18"&gt;&lt;a href="#cb3-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;                existing&lt;span class="op"&gt;.&lt;/span&gt;update&lt;span class="op"&gt;(&lt;/span&gt;from&lt;span class="op"&gt;:&lt;/span&gt; incomingEpisode&lt;span class="op"&gt;)&lt;/span&gt;  &lt;span class="co"&gt;// Update in-place!&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-19"&gt;&lt;a href="#cb3-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="op"&gt;}&lt;/span&gt; &lt;span class="cf"&gt;else&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-20"&gt;&lt;a href="#cb3-20" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;                episodes&lt;span class="op"&gt;[&lt;/span&gt;id&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; Episdoe&lt;span class="op"&gt;(&lt;/span&gt;from&lt;span class="op"&gt;:&lt;/span&gt; incomingEpisode&lt;span class="op"&gt;)&lt;/span&gt;  &lt;span class="co"&gt;// New item&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-21"&gt;&lt;a href="#cb3-21" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-22"&gt;&lt;a href="#cb3-22" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-23"&gt;&lt;a href="#cb3-23" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-24"&gt;&lt;a href="#cb3-24" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This gives me:&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;&lt;strong&gt;Granular updates:&lt;/strong&gt; When
&lt;code&gt;episode.playbackPosition&lt;/code&gt; changes, only views observing that
specific property re-render&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stable object identity:&lt;/strong&gt; The same
&lt;code&gt;Episode&lt;/code&gt; instance persists across refreshes, so SwiftUI can
diff efficiently&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single source of truth:&lt;/strong&gt; One &lt;code&gt;Episode&lt;/code&gt;
instance per ID, owned by &lt;code&gt;AppState&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="questions-youre-probably-asking"&gt;Questions You're Probably
Asking&lt;/h2&gt;
&lt;h3 id="1-why-a-class-instead-of-a-struct"&gt;1. "Why a class instead of a
struct?"&lt;/h3&gt;
&lt;p&gt;Structs are value types. When you put structs in an array and modify
one, you get a new array. SwiftUI sees a new array and re-renders
everything.&lt;/p&gt;
&lt;p&gt;Classes are reference types. When you modify a property on a class
instance, the reference stays the same. With &lt;code&gt;@Observable&lt;/code&gt;,
SwiftUI tracks which properties each view actually reads and only
re-renders when those specific properties change on the class
instance.&lt;/p&gt;
&lt;div class="sourceCode" id="cb4"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb4-1"&gt;&lt;a href="#cb4-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// With structs: entire list re-renders&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-2"&gt;&lt;a href="#cb4-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;episodes&lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="dv"&gt;0&lt;/span&gt;&lt;span class="op"&gt;].&lt;/span&gt;playbackPosition &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dv"&gt;100&lt;/span&gt;  &lt;span class="co"&gt;// Creates new array&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-3"&gt;&lt;a href="#cb4-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb4-4"&gt;&lt;a href="#cb4-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// With classes: only views reading playbackPosition re-render&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-5"&gt;&lt;a href="#cb4-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;episodes&lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;]?.&lt;/span&gt;playbackPosition &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dv"&gt;100&lt;/span&gt;  &lt;span class="co"&gt;// Same reference, property changes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="2-wont-this-cause-memory-leaks-or-retain-cycles"&gt;2. "Won't this
cause memory leaks or retain cycles?"&lt;/h3&gt;
&lt;p&gt;Not if you follow the ownership model. &lt;code&gt;AppState&lt;/code&gt; owns all
&lt;code&gt;Episode&lt;/code&gt; instances in a dictionary. Views receive references
through lookups (&lt;code&gt;appState.episode(id: episodeId)&lt;/code&gt;). There
are no closures capturing episodes, no parent-child cycles—just a flat
owner (AppState) and temporary readers (views).&lt;/p&gt;
&lt;p&gt;Do you know a better way to enforce this? Let me know, hit me up on
&lt;a href="https://indieweb.social/@myobie"&gt;mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="3-why-a-dictionary-instead-of-an-array"&gt;3. "Why a dictionary
instead of an array?"&lt;/h3&gt;
&lt;p&gt;Two reasons:&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fast lookups by ID&lt;/strong&gt; - Detail views might need to
look up episodes frequently. Make it easy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Merging is clearer&lt;/strong&gt; - When I refresh from
disk/network, I get a new set of episodes. With a dictionary, it's
obvious how to merge: update existing keys in-place, add new keys,
remove missing keys.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Also, computed properties give you arrays when you need them:&lt;/p&gt;
&lt;div class="sourceCode" id="cb5"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb5-1"&gt;&lt;a href="#cb5-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;allEpisodes&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="op"&gt;[&lt;/span&gt;Episode&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-2"&gt;&lt;a href="#cb5-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    episodes&lt;span class="op"&gt;.&lt;/span&gt;values&lt;span class="op"&gt;.&lt;/span&gt;sorted &lt;span class="op"&gt;{&lt;/span&gt; $&lt;span class="fl"&gt;0.&lt;/span&gt;date &lt;span class="op"&gt;&amp;gt;&lt;/span&gt; $&lt;span class="fl"&gt;1.&lt;/span&gt;date &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-3"&gt;&lt;a href="#cb5-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Don't sleep on computed properties: those are observable too.&lt;/p&gt;
&lt;h3
id="4-what-about-thread-safety-you-marked-episode-as-unchecked-sendable"&gt;4.
"What about thread safety? You marked Episode as @unchecked
Sendable."&lt;/h3&gt;
&lt;p&gt;Yes, and this is intentional. &lt;code&gt;Episode&lt;/code&gt; is &lt;em&gt;only&lt;/em&gt;
mutated on the main actor, through &lt;code&gt;AppState&lt;/code&gt; (which is
&lt;code&gt;@MainActor&lt;/code&gt;). The &lt;code&gt;@unchecked Sendable&lt;/code&gt;
conformance lets us pass episodes across actor boundaries for read-only
operations (like background parsing) without copying.&lt;/p&gt;
&lt;p&gt;The rule is simple: &lt;strong&gt;read anywhere, write only on main actor
through AppState/AppActions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I've also implemented an &lt;code&gt;EpisodeSnapshot&lt;/code&gt;
&lt;code&gt;struct&lt;/code&gt; which can be thrown across actor boundaries anytime.
Loading from disk should send &lt;code&gt;EpisodeSnapshot&lt;/code&gt;s over to the
&lt;code&gt;AppState&lt;/code&gt;, for example, to cross the boundary. Same with
persisting episodes to disk.&lt;/p&gt;
&lt;p&gt;Do you know a better way to enforce this? Let me know, hit me up on
&lt;a href="https://indieweb.social/@myobie"&gt;mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;h3
id="5-why-not-just-use-observationignored-on-frequently-changing-properties"&gt;5.
"Why not just use @ObservationIgnored on frequently-changing
properties?"&lt;/h3&gt;
&lt;p&gt;Because then SwiftUI doesn't see the changes at all. If
&lt;code&gt;playbackPosition&lt;/code&gt; is ignored, the progress indicator in the
UI never updates. Also, "app state" should only be the actual state. If
it isn't useful for the app, then it shouldn't be in there.&lt;/p&gt;
&lt;p&gt;The real solution is structuring views to observe only what they
display:&lt;/p&gt;
&lt;div class="sourceCode" id="cb6"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb6-1"&gt;&lt;a href="#cb6-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// Good: This view only observes title and date&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-2"&gt;&lt;a href="#cb6-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;struct&lt;/span&gt; EpisodeRow&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;View&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-3"&gt;&lt;a href="#cb6-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;episode&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; Episode&lt;/span&gt;
&lt;span id="cb6-4"&gt;&lt;a href="#cb6-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb6-5"&gt;&lt;a href="#cb6-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;body&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; some View &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-6"&gt;&lt;a href="#cb6-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        VStack &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-7"&gt;&lt;a href="#cb6-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            Text&lt;span class="op"&gt;(&lt;/span&gt;episode&lt;span class="op"&gt;.&lt;/span&gt;title&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-8"&gt;&lt;a href="#cb6-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            Text&lt;span class="op"&gt;(&lt;/span&gt;episode&lt;span class="op"&gt;.&lt;/span&gt;date&lt;span class="op"&gt;,&lt;/span&gt; style&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="op"&gt;.&lt;/span&gt;date&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-9"&gt;&lt;a href="#cb6-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-10"&gt;&lt;a href="#cb6-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-11"&gt;&lt;a href="#cb6-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-12"&gt;&lt;a href="#cb6-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb6-13"&gt;&lt;a href="#cb6-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// The playback position view is separate&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-14"&gt;&lt;a href="#cb6-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;struct&lt;/span&gt; PlaybackProgress&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;View&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-15"&gt;&lt;a href="#cb6-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;episode&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; Episode&lt;/span&gt;
&lt;span id="cb6-16"&gt;&lt;a href="#cb6-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb6-17"&gt;&lt;a href="#cb6-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;body&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; some View &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-18"&gt;&lt;a href="#cb6-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        ProgressView&lt;span class="op"&gt;(&lt;/span&gt;value&lt;span class="op"&gt;:&lt;/span&gt; Double&lt;span class="op"&gt;(&lt;/span&gt;episode&lt;span class="op"&gt;.&lt;/span&gt;playbackPosition&lt;span class="op"&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-19"&gt;&lt;a href="#cb6-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;                     total&lt;span class="op"&gt;:&lt;/span&gt; Double&lt;span class="op"&gt;(&lt;/span&gt;episode&lt;span class="op"&gt;.&lt;/span&gt;duration&lt;span class="op"&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-20"&gt;&lt;a href="#cb6-20" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-21"&gt;&lt;a href="#cb6-21" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now playback position updates only re-render
&lt;code&gt;PlaybackProgress&lt;/code&gt;, not &lt;code&gt;EpisodeRow&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="6-how-do-views-get-fresh-data-after-updates"&gt;6. "How do views
get fresh data after updates?"&lt;/h3&gt;
&lt;p&gt;Detail views should always look up from AppState:&lt;/p&gt;
&lt;div class="sourceCode" id="cb7"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb7-1"&gt;&lt;a href="#cb7-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;struct&lt;/span&gt; EpisodeDetailView&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;View&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-2"&gt;&lt;a href="#cb7-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;episodeId&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; String  &lt;span class="co"&gt;// Just the ID&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-3"&gt;&lt;a href="#cb7-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="at"&gt;@Environment&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;AppState&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="kw"&gt;self&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="kw"&gt;private&lt;/span&gt; &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;appState&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-4"&gt;&lt;a href="#cb7-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb7-5"&gt;&lt;a href="#cb7-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;private&lt;/span&gt; &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;episode&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; Episode&lt;span class="op"&gt;?&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-6"&gt;&lt;a href="#cb7-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        appState&lt;span class="op"&gt;.&lt;/span&gt;episode&lt;span class="op"&gt;(&lt;/span&gt;id&lt;span class="op"&gt;:&lt;/span&gt; episodeId&lt;span class="op"&gt;)&lt;/span&gt;  &lt;span class="co"&gt;// Always fresh&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-7"&gt;&lt;a href="#cb7-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-8"&gt;&lt;a href="#cb7-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb7-9"&gt;&lt;a href="#cb7-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;body&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; some View &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-10"&gt;&lt;a href="#cb7-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="cf"&gt;if&lt;/span&gt; &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;episode&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-11"&gt;&lt;a href="#cb7-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            Text&lt;span class="op"&gt;(&lt;/span&gt;episode&lt;span class="op"&gt;.&lt;/span&gt;title&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-12"&gt;&lt;a href="#cb7-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="co"&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-13"&gt;&lt;a href="#cb7-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-14"&gt;&lt;a href="#cb7-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb7-15"&gt;&lt;a href="#cb7-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The view holds an ID, not a stale copy. Every access goes through
AppState, which holds the authoritative instance.&lt;/p&gt;
&lt;h3 id="7-does-this-help-with-testing"&gt;7. "Does this help with
testing?"&lt;/h3&gt;
&lt;p&gt;Enormously. All state lives in &lt;code&gt;AppState&lt;/code&gt; and I bundle all
mutations in &lt;code&gt;AppActions&lt;/code&gt;. Tests don't need to render SwiftUI
at all:&lt;/p&gt;
&lt;div class="sourceCode" id="cb8"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb8-1"&gt;&lt;a href="#cb8-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;@MainActor&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb8-2"&gt;&lt;a href="#cb8-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;func&lt;/span&gt; &lt;span class="fu"&gt;testDownloadUpdatesEpisode&lt;/span&gt;&lt;span class="op"&gt;()&lt;/span&gt; &lt;span class="fu"&gt;async&lt;/span&gt; &lt;span class="kw"&gt;throws&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb8-3"&gt;&lt;a href="#cb8-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;appState&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; AppState&lt;span class="op"&gt;(...)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb8-4"&gt;&lt;a href="#cb8-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;appActions&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; AppActions&lt;span class="op"&gt;(&lt;/span&gt;appState&lt;span class="op"&gt;:&lt;/span&gt; appState&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="op"&gt;...)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb8-5"&gt;&lt;a href="#cb8-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb8-6"&gt;&lt;a href="#cb8-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="co"&gt;// Seed an episode&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb8-7"&gt;&lt;a href="#cb8-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;episode&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; Episode&lt;span class="op"&gt;(&lt;/span&gt;id&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; isDownloaded&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="kw"&gt;false&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="op"&gt;...)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb8-8"&gt;&lt;a href="#cb8-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    appState&lt;span class="op"&gt;.&lt;/span&gt;episodes&lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; episode&lt;/span&gt;
&lt;span id="cb8-9"&gt;&lt;a href="#cb8-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb8-10"&gt;&lt;a href="#cb8-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="co"&gt;// Perform action&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb8-11"&gt;&lt;a href="#cb8-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="cf"&gt;await&lt;/span&gt; appActions&lt;span class="op"&gt;.&lt;/span&gt;downloadEpisode&lt;span class="op"&gt;(&lt;/span&gt;episode&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb8-12"&gt;&lt;a href="#cb8-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb8-13"&gt;&lt;a href="#cb8-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="co"&gt;// Assert state changed&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb8-14"&gt;&lt;a href="#cb8-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    XCTAssertTrue&lt;span class="op"&gt;(&lt;/span&gt;appState&lt;span class="op"&gt;.&lt;/span&gt;episodes&lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;]?.&lt;/span&gt;isDownloaded &lt;span class="op"&gt;==&lt;/span&gt; &lt;span class="kw"&gt;true&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb8-15"&gt;&lt;a href="#cb8-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;No UI, no simulators, fast and deterministic. The views are just
projections of state, so I can just test the state.&lt;/p&gt;
&lt;h3 id="8-what-about-derivedcomputed-state-like-downloaded-episodes"&gt;8.
"What about derived/computed state like 'downloaded episodes'?"&lt;/h3&gt;
&lt;p&gt;Keep derived state as computed properties on
&lt;code&gt;AppState&lt;/code&gt;:&lt;/p&gt;
&lt;div class="sourceCode" id="cb9"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb9-1"&gt;&lt;a href="#cb9-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;downloadedEpisodes&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="op"&gt;[&lt;/span&gt;Episode&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb9-2"&gt;&lt;a href="#cb9-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    episodes&lt;span class="op"&gt;.&lt;/span&gt;values&lt;span class="op"&gt;.&lt;/span&gt;filter &lt;span class="op"&gt;{&lt;/span&gt; $&lt;span class="fl"&gt;0.&lt;/span&gt;isDownloaded &lt;span class="op"&gt;}.&lt;/span&gt;sorted &lt;span class="op"&gt;{&lt;/span&gt; $&lt;span class="fl"&gt;0.&lt;/span&gt;date &lt;span class="op"&gt;&amp;gt;&lt;/span&gt; $&lt;span class="fl"&gt;1.&lt;/span&gt;date &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb9-3"&gt;&lt;a href="#cb9-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb9-4"&gt;&lt;a href="#cb9-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb9-5"&gt;&lt;a href="#cb9-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;var&lt;/span&gt; &lt;span class="va"&gt;newEpisodes&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="op"&gt;[&lt;/span&gt;Episode&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb9-6"&gt;&lt;a href="#cb9-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    episodes&lt;span class="op"&gt;.&lt;/span&gt;values&lt;span class="op"&gt;.&lt;/span&gt;filter &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="op"&gt;!&lt;/span&gt;$&lt;span class="fl"&gt;0.&lt;/span&gt;hasPlayed &lt;span class="op"&gt;}.&lt;/span&gt;sorted &lt;span class="op"&gt;{&lt;/span&gt; $&lt;span class="fl"&gt;0.&lt;/span&gt;date &lt;span class="op"&gt;&amp;gt;&lt;/span&gt; $&lt;span class="fl"&gt;1.&lt;/span&gt;date &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb9-7"&gt;&lt;a href="#cb9-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These recompute on access, but because &lt;code&gt;@Observable&lt;/code&gt;
tracks reads, a view that only accesses &lt;code&gt;downloadedEpisodes&lt;/code&gt;
won't re-render when &lt;code&gt;newEpisodes&lt;/code&gt; would change (unless the
underlying episodes data changed in a way that affects both). You can
move to a memoized version if you find you need to optimize, but wait
IMO.&lt;/p&gt;
&lt;h3 id="9-how-do-you-handle-navigation-with-reactive-data"&gt;9. "How do
you handle navigation with reactive data?"&lt;/h3&gt;
&lt;p&gt;Navigate by ID, not by object:&lt;/p&gt;
&lt;div class="sourceCode" id="cb10"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb10-1"&gt;&lt;a href="#cb10-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// Good: Pass ID&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb10-2"&gt;&lt;a href="#cb10-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;NavigationLink&lt;span class="op"&gt;(&lt;/span&gt;value&lt;span class="op"&gt;:&lt;/span&gt; NavigationRoute&lt;span class="op"&gt;.&lt;/span&gt;episode&lt;span class="op"&gt;(&lt;/span&gt;episode&lt;span class="op"&gt;.&lt;/span&gt;id&lt;span class="op"&gt;))&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb10-3"&gt;&lt;a href="#cb10-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    EpisodeRow&lt;span class="op"&gt;(&lt;/span&gt;episode&lt;span class="op"&gt;:&lt;/span&gt; episode&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb10-4"&gt;&lt;a href="#cb10-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb10-5"&gt;&lt;a href="#cb10-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb10-6"&gt;&lt;a href="#cb10-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// In destination&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb10-7"&gt;&lt;a href="#cb10-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;.&lt;/span&gt;navigationDestination&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="cf"&gt;for&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; NavigationRoute&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="kw"&gt;self&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt; route &lt;span class="cf"&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb10-8"&gt;&lt;a href="#cb10-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="cf"&gt;switch&lt;/span&gt; route &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb10-9"&gt;&lt;a href="#cb10-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="cf"&gt;case&lt;/span&gt; &lt;span class="op"&gt;.&lt;/span&gt;episode&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="va"&gt;id&lt;/span&gt;&lt;span class="op"&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb10-10"&gt;&lt;a href="#cb10-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        EpisodeDetailView&lt;span class="op"&gt;(&lt;/span&gt;episodeId&lt;span class="op"&gt;:&lt;/span&gt; id&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb10-11"&gt;&lt;a href="#cb10-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb10-12"&gt;&lt;a href="#cb10-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The detail view looks up fresh data from AppState using the ID. Even
if the episode updates while navigating, the detail view sees the
current state.&lt;/p&gt;
&lt;h3 id="10-what-about-high-frequency-updates-like-playback-progress"&gt;10.
"What about high-frequency updates like playback progress?"&lt;/h3&gt;
&lt;p&gt;Throttle before mutating state. I use Combine to debounce playback
callbacks:&lt;/p&gt;
&lt;div class="sourceCode" id="cb11"&gt;&lt;pre
class="sourceCode swift"&gt;&lt;code class="sourceCode swift"&gt;&lt;span id="cb11-1"&gt;&lt;a href="#cb11-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// AudioPlayerService sends callbacks frequently&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb11-2"&gt;&lt;a href="#cb11-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// A Combine pipeline throttles to ~250ms before updating AppState&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb11-3"&gt;&lt;a href="#cb11-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;playerProgressPublisher&lt;/span&gt;
&lt;span id="cb11-4"&gt;&lt;a href="#cb11-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;.&lt;/span&gt;throttle&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="cf"&gt;for&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="op"&gt;.&lt;/span&gt;milliseconds&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="dv"&gt;250&lt;/span&gt;&lt;span class="op"&gt;),&lt;/span&gt; scheduler&lt;span class="op"&gt;:&lt;/span&gt; DispatchQueue&lt;span class="op"&gt;.&lt;/span&gt;main&lt;span class="op"&gt;,&lt;/span&gt; latest&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="kw"&gt;true&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb11-5"&gt;&lt;a href="#cb11-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;.&lt;/span&gt;sink &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="kw"&gt;weak&lt;/span&gt; &lt;span class="kw"&gt;self&lt;/span&gt;&lt;span class="op"&gt;]&lt;/span&gt; time &lt;span class="cf"&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb11-6"&gt;&lt;a href="#cb11-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="kw"&gt;self&lt;/span&gt;&lt;span class="op"&gt;?.&lt;/span&gt;appState&lt;span class="op"&gt;.&lt;/span&gt;currentTime &lt;span class="op"&gt;=&lt;/span&gt; time&lt;/span&gt;
&lt;span id="cb11-7"&gt;&lt;a href="#cb11-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The audio callback fires 60x/second. AppState updates 4x/second. The
UI is smooth, and you're not burning CPU on excessive re-renders.&lt;/p&gt;
&lt;h3 id="11-how-does-this-compare-to-reduxtca"&gt;11. "How does this compare
to Redux/TCA?"&lt;/h3&gt;
&lt;p&gt;Similar in spirit, different in mechanics. Like Redux/TCA:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Single source of truth (AppState)&lt;/li&gt;
&lt;li&gt;Unidirectional flow (Action → State → UI)&lt;/li&gt;
&lt;li&gt;Testable without UI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unlike Redux/TCA:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No action types or reducers—just methods on AppActions&lt;/li&gt;
&lt;li&gt;Native Swift observation instead of custom subscription system&lt;/li&gt;
&lt;li&gt;Reference types for items that need granular updates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's less ceremony, more direct mutation, but the same core
principles.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Any other questions I missed? Hit me up on &lt;a
href="https://indieweb.social/@myobie"&gt;mastodon&lt;/a&gt;.&lt;/p&gt;</description>
    </item>

    <item>
      <title>I Don't Have a CMS Anymore</title>
      <link>https://nathanherald.com/posts/no-cms/</link>
      <pubDate>Tue, 18 Nov 2025 15:47:19 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/no-cms/</guid>
      <description>&lt;p&gt;I used to run this site on &lt;a href="https://gohugo.io"&gt;hugo&lt;/a&gt;.
Standard static site generator setup: write markdown, run
&lt;code&gt;hugo build&lt;/code&gt;, preview locally, deploy. It worked fine, until
it didn’t. A hugo update would break publishing new posts and I would
let my site sit broken for months. Or trying to get the HTML how I
wanted would require me to learn about shortcodes or weird go template
language stuff…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I deleted all of that.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now my website is just HTML files. No CMS at all.&lt;/p&gt;
&lt;p&gt;When I want to publish a post, I write a markdown or HTML file and
then tell &lt;code&gt;claude&lt;/code&gt;: "publish it.” That's it.&lt;/p&gt;
&lt;p&gt;Claude Code has built up its own scripts to convert the markdown to
HTML, update the RSS feed, add the post to the homepage and archive, run
all the formatting tools, and handle everything else. I don't touch any
of it. I've never even looked at those scripts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is an experiment for me: how far can I push a coding
agent?&lt;/strong&gt; Can we eliminate traditional tooling entirely? Can the
robot just do everything "by hand"?&lt;/p&gt;
&lt;p&gt;Static site generators exist because manually maintaining a website,
keeping an RSS feed in sync, updating navigation, managing archives,
etc, is tedious and error-prone.&lt;/p&gt;
&lt;p&gt;But &lt;strong&gt;what if the automation doesn't need to be baked into
tooling?&lt;/strong&gt; What if it can just be... done, on demand, by an
agent? What if we can conjure specific, tailored tools when we need
them?&lt;/p&gt;
&lt;p&gt;No build step. No framework. No abstractions. Just files.&lt;/p&gt;
&lt;p&gt;I still write the posts. I wrote this one.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;claude&lt;/code&gt; does everything else.&lt;/p&gt;
&lt;p&gt;This is the same pattern as &lt;a href="/posts/libraries-have-staff"&gt;the
library staff metaphor I wrote about recently&lt;/a&gt;. I'm the user of the
library. Claude Code is the staff keeping everything organized and
accessible for me.&lt;/p&gt;
&lt;p&gt;I didn’t realize the anxiety of just dealing with hugo was keeping me
from posting. Now, I don’t have an excuse. It’s way too easy to throw
some markdown over to the thing and let it figure it out.&lt;/p&gt;
&lt;h2 id="take-aways"&gt;Take aways&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Agents can replace more infrastructure than I
expected.&lt;/strong&gt; I thought I'd still need some sort of framework or
some kind of build system. Turns out those can be coded up on-demand.
And if I change my mind, new tools are produced and run
just-in-time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simple systems are easier to maintain.&lt;/strong&gt; The less
abstraction, the more directly Claude Code can work. Bash scripts and
HTML files are transparent. Claude can read them, understand them,
modify them. No nested packages or dependencies. No magic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The agent maintains its own tools.&lt;/strong&gt; It built the
publishing scripts, and now it maintains them. When requirements change,
It updates the code. What a weird future we have created.&lt;/p&gt;
&lt;h2 id="future-experiments"&gt;Future experiments&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Local, private coding agent.&lt;/strong&gt; I don’t like sending up
tons of data to Anthropic just to publish a blog post. Can I make this
work with a local model on my laptop? I’m testing that out when I get
time…&lt;/p&gt;
&lt;h2 id="markdown-crown"&gt;Markdown 👑&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Now, my website is simple.&lt;/em&gt; A handful of posts, static pages,
an RSS feed. No complex taxonomy, no search, no comments, no dynamic
features.&lt;/p&gt;
&lt;p&gt;If I needed those things, I might need real tooling again. Or I might
not. Maybe a coding agent can just build that stuff too. I honestly
don't know where the line is yet.&lt;/p&gt;
&lt;p&gt;Maybe we just need markdown files and a really good agent.&lt;/p&gt;</description>
    </item>

    <item>
      <title>The Library Doesn't Just Have a Card Catalog… it has a full-time staff</title>
      <link>https://nathanherald.com/posts/libraries-have-staff/</link>
      <pubDate>Tue, 18 Nov 2025 15:18:16 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/libraries-have-staff/</guid>
      <description>&lt;p&gt;A few years ago, &lt;a
href="https://nathanherald.com/posts/digital-card-catalog/"&gt;I wrote
about how the library's card catalog is the right metaphor for
organizing our digital information&lt;/a&gt;. The physical catalog works
because it connects all our information exactly how we need for our
personal knowledge bases.&lt;/p&gt;
&lt;p&gt;But I missed something fundamental: &lt;strong&gt;the card catalog in a
real Library doesn’t maintain itself.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Behind every well-organized library is &lt;strong&gt;a team of
people&lt;/strong&gt; constantly working to keep information accessible.
They’re cataloging new acquisitions, updating subject headings,
cross-referencing related materials, culling outdated items, all
&lt;em&gt;before&lt;/em&gt; you ask. When you walk into a library and find exactly
what you’re looking for, it’s because of the wonderful staff there.&lt;/p&gt;
&lt;p&gt;The reason we struggle with digital organization isn’t that we lack
filing systems: there are too many &lt;em&gt;systems&lt;/em&gt; out there to count.
It’s that &lt;strong&gt;we’re trying to be both the user and the entire
staff,&lt;/strong&gt; and we simply don’t have the time.&lt;/p&gt;
&lt;h2 id="what-library-staff-actually-do"&gt;What Library Staff Actually
Do&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Cataloging&lt;/strong&gt;: Every new item gets metadata assigned:
and not just title and author, but subject headings, classifications,
and keywords.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cross-referencing&lt;/strong&gt;: Related materials get linked. If
you’re checkout out the Cold War, the librarian has already connected
biographies, primary source documents, and relevant periodicals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Collection maintenance&lt;/strong&gt;: Old, outdated, or
rarely-used items get evaluated. The collection stays relevant through
&lt;strong&gt;active curation, not just accumulation.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Discovery support&lt;/strong&gt;: Librarians create displays,
reading lists, and subject guides that surface relevant materials you
didn’t know to ask for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Answer questions&lt;/strong&gt;: When search isn’t cutting it, a
human who understands the collection and knows where things are can
help.&lt;/p&gt;
&lt;p&gt;All of this is &lt;strong&gt;proactive&lt;/strong&gt;. The organization work
happens continuously in the background, an act of renewal.&lt;/p&gt;
&lt;h2 id="weve-been-trying-to-do-this-ourselves"&gt;We’ve Been Trying to Do
This Ourselves&lt;/h2&gt;
&lt;p&gt;With all of our notes, documents, bookmarks, photos, and emails, we
are attempting to be our own librarians. We make folders. We add tags
when we remember. We try to search when we need something. We promise
ourselves we’ll organize everything soon.&lt;/p&gt;
&lt;p&gt;It doesn’t work because the ratio is impossible: one person cannot be
both a library’s sole user and its entire staff. The cataloging work
scales with the collection size and our available time does not.&lt;/p&gt;
&lt;p&gt;Every productivity system eventually fails for this reason. We build
the perfect folder hierarchy, create the ultimate tagging system,
install the best knowledge management software; then life happens. Files
pile up. Tags become inconsistent. The system that was supposed to save
us time becomes another thing we feel guilty about not maintaining.&lt;/p&gt;
&lt;p&gt;The problem isn’t our discipline – the problem is structural.&lt;/p&gt;
&lt;h2 id="a-personal-library-staff"&gt;A Personal Library Staff?&lt;/h2&gt;
&lt;p&gt;For the first time, we can actually build something resembling “a
proactive staff.”&lt;/p&gt;
&lt;p&gt;Cloud AI &lt;a
href="https://simonwillison.net/2025/Sep/18/agents/"&gt;agents&lt;/a&gt; can do
some of this work, but at the expense of sending all our private
information up to their servers constantly…&lt;/p&gt;
&lt;p&gt;So recently &lt;strong&gt;I’ve been learning about and working with local
models,&lt;/strong&gt; the ones you can run on your phone or laptop in a truly
private, offline way. I’ve had success doing the systematic, repetitive
organization work that can improve our personal knowledge systems and
finally make them workable over time.&lt;/p&gt;
&lt;p&gt;Here’s what I’ve learned is possible: I can build agents that process
new documents as they arrive, extracting metadata and assigning
categories based on my existing system. When patterns emerge, they
suggest new categories I hadn’t thought of. I’ve programmed experiments
to scan my collection for connections – linking meeting notes to project
documents, research papers to relevant bookmarks, photos to calendar
events.&lt;/p&gt;
&lt;p&gt;They can handle maintenance too. Flagging duplicates, identifying
outdated information, highlighting orphaned items that have no
connections to anything else. As documents move into my archive, agents
generate abstracts so I don’t lose track of what I once knew. They
create reading lists organized by theme, produce regular digests of what
I’ve saved recently.&lt;/p&gt;
&lt;p&gt;And they can “learn” from how I search. Every query I run is
information about what matters to me and how I think about my personal
context. An agent can use that to preemptively improve discoverability
of materials I’ll likely need, without me having to stop and manually
organize everything first.&lt;/p&gt;
&lt;p&gt;None of this requires me to stop what I’m doing to organize. The
agents can work in the background.&lt;/p&gt;
&lt;h2 id="private-and-local-is-the-future"&gt;Private and local is the
future&lt;/h2&gt;
&lt;p&gt;Your personal context contains your most sensitive information: work
documents, personal notes, financial records, health information, etc.
Sending this to a cloud AI service doesn’t feel right. And we don’t have
to.&lt;/p&gt;
&lt;p&gt;A Mac Mini can run models that will handle this work. No
subscriptions. No data leaving your network. Your local private “staff”
works for you, not for the cloud provider that trained them.&lt;/p&gt;
&lt;h3 id="specialized-agents-not-one-super-agent"&gt;Specialized agents, not
one super-agent&lt;/h3&gt;
&lt;p&gt;Local models are small, so it is prudent to build many small agents
instead of one big one.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;filing agent&lt;/strong&gt; that runs daily to categorize new
items&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;connection agent&lt;/strong&gt; that finds relationships between
materials&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;maintenance agent&lt;/strong&gt; that flags issues and
duplicates&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;summary agent&lt;/strong&gt; that creates abstracts and
digests&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;search agent&lt;/strong&gt; that learns from your queries to
improve indexing&lt;/li&gt;
&lt;li&gt;Etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each agent should have focused instructions tailored to your specific
organization system. You’re not prompting ChatGPT to “organize my
files.” You’re running custom, tailored local programs that know your
taxonomy, your terminology, and your priorities.&lt;/p&gt;
&lt;p&gt;Every time you search for something or ask a question, you’re
providing data about what matters to you and how you think about your
information. Every time you manually connect two documents, you’re
teaching the system about relationships it should look for. Every time
you can’t find something, you’re revealing a gap in the
organization.&lt;/p&gt;
&lt;p&gt;These patterns should become instructions for proactive work. If you
frequently search for “client feedback from Q3,” your agents should
learn to automatically tag and group client feedback by quarter. If you
often link legal documents to project files, your agents should start
suggesting those connections preemptively.&lt;/p&gt;
&lt;h2 id="local-agents-change-the-game"&gt;Local Agents Change the Game&lt;/h2&gt;
&lt;p&gt;The library card catalog works because it is maintained by people
whose full-time job is keeping information accessible. Personal
knowledge management has failed for most of us because we’re trying to
do that work ourselves while also being the library’s primary user.&lt;/p&gt;
&lt;p&gt;Local, private agents can solve this by becoming your library staff.
They can do the proactive, systematic organization work in the
background. They can learn from your usage patterns. They can run
continuously without requiring your attention. And because they run
locally, your information stays yours.&lt;/p&gt;
&lt;p&gt;You don't need better organizational discipline. You need staff. And
for the first time, you might can actually have them.&lt;/p&gt;
&lt;p&gt;I'm building toward this vision in a product called &lt;a
href="https://new.space/app"&gt;new.space&lt;/a&gt;. Run local models on your
iPhone today. Give it a try.&lt;/p&gt;</description>
    </item>

    <item>
      <title>The Capability Gradient</title>
      <link>https://nathanherald.com/posts/capability-gradient/</link>
      <pubDate>Tue, 04 Nov 2025 11:50:04 +0100</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/capability-gradient/</guid>
      <description>&lt;h1 id="the-capability-gradient"&gt;The Capability Gradient&lt;/h1&gt;
&lt;p&gt;I've been thinking about how I feel great when I dive deep into a
topic, to expand my knowledge and capabilities in an area, and I think I
finally can explain my thought model in a simple framework. I call it
the &lt;em&gt;Capability Gradient&lt;/em&gt; and it has four levels:&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;&lt;strong&gt;Consume&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Curate&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collaborate&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If I need a new chair, I might just go to IKEA. Simple and done.
&lt;em&gt;Or&lt;/em&gt;, I can go further. I can shop around, I can do research, I
can learn about the history of chairs, I can develop my taste, and then
I can make a more informed decision. &lt;em&gt;Or&lt;/em&gt;, I can go further
still. I can contact a carpenter and collaborate, my taste can be
instilled into the design. &lt;em&gt;Or&lt;/em&gt;, I can go further, I can become
an apprentice, practice, and then finally build my own chair.&lt;/p&gt;
&lt;p&gt;As a basic consumer, I can only have what is offered in my nearby
stores. Developing taste through curation: I can advise my friends and
my future self on options. Through collaboration I can customize. And
through mastery I complete freedom to iterate.&lt;/p&gt;
&lt;p&gt;The deeper you go, the more adaptable you become. And you’re building
capability for &lt;em&gt;your own future ideas&lt;/em&gt; – the ones you haven’t had
yet. You can’t know which current ideas will work or what better ideas
you’ll have next month. If you’ve only bought from IKEA, you’re locked
in. If you’ve built chairs yourself, you can pivot when inspiration
strikes.&lt;/p&gt;
&lt;p&gt;Research on &lt;a
href="https://www.pursuit-of-happiness.org/science-of-happiness/getting-in-the-flow/"&gt;flow
states&lt;/a&gt; shows people experience &lt;a
href="https://www.robinwieruch.de/lessons-learned-deep-work-flow/"&gt;the
most satisfaction&lt;/a&gt; when deeply engaged in challenging work that
matches their skill level – not mindlessly repeating tasks. The assembly
line worker who just repeats motions gets bored. The one who pushes
deeper, builds mastery. &lt;a
href="https://planyway.com/blog/mihaly-csikszentmihalyi-flow-theory"&gt;They
find flow.&lt;/a&gt; Going deep isn’t just strategic – it can be more
satisfying.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However, not everyone wants to climb to “create” on every
gradient.&lt;/strong&gt; Some people thrive on variety and get bored going
deep. &lt;a
href="https://www.researchgate.net/publication/260404911_Mastery_in_happiness_-_the_role_of_expertise_in_everyday_life"&gt;Others
love mastery.&lt;/a&gt; The key is knowing where you’re most comfortable and
being intentional about it. &lt;strong&gt;Maybe you’re naturally a curator.
That’s great!&lt;/strong&gt; The point isn’t that everyone should become a
craftsman – it’s that you can choose your depth deliberately,
understanding the tradeoffs and understanding yourself. When something
really matters to you, when you want maximum adaptability for future
ideas, that’s when you can push deeper.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Food:&lt;/strong&gt; Restaurants → farmers markets → cooking
classes → growing your own&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Software:&lt;/strong&gt; Use apps → compare tools → customize
workflows → build your own&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content:&lt;/strong&gt; Consume → curate/share → remix → create
original&lt;/li&gt;
&lt;li&gt;Etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This isn’t about doing everything yourself or becoming a
jack-of-all-trades. It’s about strategically and intentionally choosing
where to build capability – where you want the freedom to adapt,
iterate, and respond to ideas you haven’t had yet.&lt;/p&gt;
&lt;p&gt;Thinking through this framework has helped me understand where I am
most content and happy, and why I behave the way I do sometimes, maybe
it can help you as well.&lt;/p&gt;</description>
    </item>

    <item>
      <title>TIL about Git Notes</title>
      <link>https://nathanherald.com/posts/til/git-notes/</link>
      <pubDate>Thu, 23 Oct 2025 14:42:52 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/til/git-notes/</guid>
      <description>&lt;p&gt;Git has a notes command that lets you add annotations to commits
without changing the commit itself. Pretty handy for adding metadata or
references after the fact!&lt;/p&gt;</description>
    </item>

    <item>
      <title>TIL about strudel</title>
      <link>https://nathanherald.com/posts/til/strudel/</link>
      <pubDate>Sun, 14 Sep 2025 22:35:07 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/til/strudel/</guid>
      <description>&lt;p&gt;&lt;abbr title="I saw this!"&gt;👀&lt;/abbr&gt;&lt;/p&gt;
&lt;iframe width="315" height="560" src="https://www.youtube-nocookie.com/embed/8zmy8Amb9ro" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;These are the real Hackers! I am blown away by how great the live
coding performances are with this tool. Checkout the examples that play
right in your browser in the docs at &lt;a
href="https://strudel.cc/workshop/getting-started/#examples"&gt;strudel.cc/workshop/getting-started&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I made a song, which you can see the code and play right here!&lt;/p&gt;
&lt;iframe
  src="https://strudel.cc/#c2V0Y3BtKDMwKQoKbGV0IGFycCA9IG4oYAo8CiAgWwogICAgPFswIDEgMiAzIDUgNiA4IDldPgogICAgPFswIDEgMiAzIDUgNiA4IDEwXT4KICBdCiAgWwogICAgPFswIDEgMiAzIDUgNiA3IDEwXT4KICAgIDxbMCAxIDIgMyA1IDYgOCA5XT4KICBdCiAgWwogICAgPFswIDEgMiAzIDQgNSA2IDddPgogICAgPFswIDEgMiAzIDQgNSA2IDldPgogIF0KICBbCiAgICA8WzAgMSAyIDMgNSA2IDkgMTBdPgogICAgPFswIDEgMiAzIDUgNiA4IDldPgogIF0KPgpgKQogIC5jaG9yZCgiPENtIEFiIEZtIEViPiIpCiAgLm1vZGUoImFib3ZlOmMzIikKICAudm9pY2luZygpCiAgLnNvdW5kKCJ0cmlhbmdsZSIpCiAgLmNsaXAoLjQpCiAgLmNob3J1cyguNCkKICAuYWRzcigiMC4wMTouMTU6Ljc6LjIiKQogIC5nYWluKHNpbmUucmFuZ2UoMC40LCAwLjUpLnNsb3coMTApKQogIC5kZWxheSgwLjA1KQogIC5fcHVuY2hjYXJkKCkKCgpsZXQgYXJwMiA9IG4oYAo8CiAgWwogICAgPFswIDIgNSA2XT4KICAgIDxbMCAyIDYgNV0%2BCiAgXQogIFsKICAgIDxbMCAyIDUgNl0%2BCiAgICA8WzAgMiA2IDVdPgogIF0KICBbCiAgICA8WzAgMiA0IDVdPgogICAgPFswIDIgNCA1XT4KICBdCiAgWwogICAgPFswIDIgNSA2XT4KICAgIDxbMCAyIDYgNV0%2BCiAgXQo%2BCmApCiAgLmNob3JkKCJDbSIpCiAgLm1vZGUoImFib3ZlOmMzIikKICAudm9pY2luZygpCiAgLnNvdW5kKCJ0cmlhbmdsZSIpCiAgLmFkc3IoIi4wNToxOi43OjIiKQogIC5nYWluKC41KQoKCgoKbGV0IHBhZCA9CiAgY2hvcmQoYAogICAgPENtIEZtPiwKICAgIDxDbSBGbT4KICBgKQogICAgLnZvaWNpbmcoKQogICAgLmxheWVyKHYgPT4gdi5zb3VuZCgidHJpYW5nbGUiKS5scGYoMjIwMCkuZ2FpbiguMjIpKQogICAgLmxheWVyKHYgPT4gdi5zb3VuZCgic2luZSIpLmxwZigxODAwKS5nYWluKC4xMikpCiAgICAuYWRzcigiMS41OjM6Ljc1OjUiKQogICAgLnBhbigiMC4yLDAuOCIpCiAgICAuZ2FpbihzaW5lLnJhbmdlKDAuMDEsIDAuMDIpLnNsb3coMTYpKQogICAgLmNob3J1cyguNSkKICAgIC5yb29tKC43KS5zaXplKC45NSkKICAgIC5kZXR1bmUoIjAsMC4xIikKICAgIC5kZWxheSgiMC44IikKICAgIC5zbG93KDgpCiAgICAuX3B1bmNoY2FyZCgpCgpsZXQgd2F2ZXMgPSBzdGFjaygKICBub2lzZSgwLjcpCiAgICAuc291bmQoInBpbmsiKQogICAgLmhwZig0MCkubHBmKDQwMCkKICAgIC5nYWluKHNpbmUucmFuZ2UoLjQsLjYpLnNsb3coMTYpKQogICAgLnJvb20oLjgpLnNpemUoMSksCgogIG5vaXNlKDAuNykKICAgIC5zb3VuZCgicGluayIpCiAgICAuaHBmKDQwMCkubHBmKDMwMDApCiAgICAuZ2FpbihzaW5lLnJhbmdlKC4yLC4zKS5zbG93KDEyKSkKICAgIC5wYW4oc2luZS5yYW5nZSgwLDEpLnNsb3coMjQpKQogICAgLnJvb20oLjg1KS5zaXplKC45NSksCiAgCiAgbm9pc2UoMC43KQogICAgLnNvdW5kKCJwaW5rIikKICAgIC5ocGYoMzAwMCkubHBmKDkwMDApCiAgICAuZ2FpbihzaW5lLnJhbmdlKC4xLC4yKS5zbG93KDEyKSkKICAgIC5wYW4ocGVybGluLnJhbmdlKDAsMSkuc2xvdygxOCkpCiAgICAucm9vbSguOSkuc2l6ZSgxKQogICAgLmRlZ3JhZGVCeSguMSksCiAgCiAgbm9pc2UoMC43KQogICAgLnNvdW5kKCJwaW5rIikKICAgIC5ocGYoNjAwKS5scGYoNDAwMCkKICAgIC5hZHNyKCIuMjowLjU6MDoxIikKICAgIC5nYWluKC4yNSkKICAgIC5zdHJ1Y3QoInggfiB%2BIH4iKQogICAgLnBseSg4KQogICAgLnBhbihyYW5kKQogICAgLnNsb3coOC4zKQopLmRlbGF5KDAuNykuZ2FpbihzaW5lLnJhbmdlKDAuMTUsIDAuMjUpLnNsb3coOCkpLl9wdW5jaGNhcmQoKQoKbGV0IGJhc2UgPSBub3RlKCJjMiIpLnNvdW5kKCJzaW5lIikKICAuYWRzcigiNDoxMjouNTo4IikKICAuZ2FpbiguOCkKICAucm9vbSguOCkuc2l6ZSguOSkKICAuc2xvdyg4KQogIC5fcHVuY2hjYXJkKCkKCmxldCBjaGltZSA9IG4oIjAgNCA3IDExIikuc2NhbGUoIkM6bWlub3IiKQogIC5zb3VuZCgidHJpYW5nbGUiKQogIC5hZHNyKCIwLjAxOjI6Ljc6NSIpCiAgLmRlbGF5KCIuMzouMzU6LjYiKQogIC5nYWluKC4xNSkKICAuc29tZXRpbWVzQnkoLjcsIHggPT4geC5zdHJ1Y3QoIn4iKSkgIC8vIHBsYXkgbGVzcyBvZnRlbgogIC5zbG93KDQpCiAgLl9wdW5jaGNhcmQoKQoKbGV0IGRydW0gPSBzKCJiZCB%2BIH4gfiIpLmJhbmsoIlJvbGFuZFRSOTA5IikKICAuZ2FpbiguMikKICAucm9vbSguNykuc2l6ZSguOSkKICAuZGVncmFkZUJ5KC40KQogIC5zbG93KDIpCiAgLmRlbGF5KCIwLjY1OjE6MSIpCiAgLl9wdW5jaGNhcmQoKQoKJDogYXJyYW5nZSgKICBbNCwgYXJwXSwKICBbOCwgc3RhY2soYXJwLCB3YXZlcywgYmFzZSwgZHJ1bSldLAogIFsxNiwgc3RhY2soYXJwLCB3YXZlcywgYmFzZSwgZHJ1bSwgcGFkLCBjaGltZSldLAogIFs4LCBzdGFjayhhcnAyLCB3YXZlcywgYmFzZSwgZHJ1bSwgcGFkLCBjaGltZSldLAogIFsxNiwgc3RhY2soYXJwLCB3YXZlcywgYmFzZSwgZHJ1bSwgcGFkLCBjaGltZSldLAogIFs4LCBzdGFjayh3YXZlcywgYmFzZSwgZHJ1bSldLAogIFs0LCBzdGFjayh3YXZlcyldLAopCgokOiBscGYoc2luZS5yYW5nZSg5MDAsIDI4MDApLnNsb3coMTYpKQ%3D%3D"
  width="600"
  height="300"
&gt;&lt;/iframe&gt;

&lt;p&gt;What an incredible tool. Mind blowing.&lt;/p&gt;</description>
    </item>

    <item>
      <title>TIL about strudel</title>
      <link>https://nathanherald.com/posts/til/strudel/index/</link>
      <pubDate>Sun, 14 Sep 2025 22:35:07 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/posts/til/strudel/index/</guid>
      <description>&lt;p&gt;&lt;abbr title="I saw this!"&gt;👀&lt;/abbr&gt;&lt;/p&gt;
&lt;iframe width="315" height="560" src="https://www.youtube-nocookie.com/embed/8zmy8Amb9ro" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;These are the real Hackers! I am blown away by how great the live
coding performances are with this tool. Checkout the examples that play
right in your browser in the docs at &lt;a
href="https://strudel.cc/workshop/getting-started/#examples"&gt;strudel.cc/workshop/getting-started&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I made a song, which you can see the code and play right here!&lt;/p&gt;
&lt;iframe
  src="https://strudel.cc/#c2V0Y3BtKDMwKQoKbGV0IGFycCA9IG4oYAo8CiAgWwogICAgPFswIDEgMiAzIDUgNiA4IDldPgogICAgPFswIDEgMiAzIDUgNiA4IDEwXT4KICBdCiAgWwogICAgPFswIDEgMiAzIDUgNiA3IDEwXT4KICAgIDxbMCAxIDIgMyA1IDYgOCA5XT4KICBdCiAgWwogICAgPFswIDEgMiAzIDQgNSA2IDddPgogICAgPFswIDEgMiAzIDQgNSA2IDldPgogIF0KICBbCiAgICA8WzAgMSAyIDMgNSA2IDkgMTBdPgogICAgPFswIDEgMiAzIDUgNiA4IDldPgogIF0KPgpgKQogIC5jaG9yZCgiPENtIEFiIEZtIEViPiIpCiAgLm1vZGUoImFib3ZlOmMzIikKICAudm9pY2luZygpCiAgLnNvdW5kKCJ0cmlhbmdsZSIpCiAgLmNsaXAoLjQpCiAgLmNob3J1cyguNCkKICAuYWRzcigiMC4wMTouMTU6Ljc6LjIiKQogIC5nYWluKHNpbmUucmFuZ2UoMC40LCAwLjUpLnNsb3coMTApKQogIC5kZWxheSgwLjA1KQogIC5fcHVuY2hjYXJkKCkKCgpsZXQgYXJwMiA9IG4oYAo8CiAgWwogICAgPFswIDIgNSA2XT4KICAgIDxbMCAyIDYgNV0%2BCiAgXQogIFsKICAgIDxbMCAyIDUgNl0%2BCiAgICA8WzAgMiA2IDVdPgogIF0KICBbCiAgICA8WzAgMiA0IDVdPgogICAgPFswIDIgNCA1XT4KICBdCiAgWwogICAgPFswIDIgNSA2XT4KICAgIDxbMCAyIDYgNV0%2BCiAgXQo%2BCmApCiAgLmNob3JkKCJDbSIpCiAgLm1vZGUoImFib3ZlOmMzIikKICAudm9pY2luZygpCiAgLnNvdW5kKCJ0cmlhbmdsZSIpCiAgLmFkc3IoIi4wNToxOi43OjIiKQogIC5nYWluKC41KQoKCgoKbGV0IHBhZCA9CiAgY2hvcmQoYAogICAgPENtIEZtPiwKICAgIDxDbSBGbT4KICBgKQogICAgLnZvaWNpbmcoKQogICAgLmxheWVyKHYgPT4gdi5zb3VuZCgidHJpYW5nbGUiKS5scGYoMjIwMCkuZ2FpbiguMjIpKQogICAgLmxheWVyKHYgPT4gdi5zb3VuZCgic2luZSIpLmxwZigxODAwKS5nYWluKC4xMikpCiAgICAuYWRzcigiMS41OjM6Ljc1OjUiKQogICAgLnBhbigiMC4yLDAuOCIpCiAgICAuZ2FpbihzaW5lLnJhbmdlKDAuMDEsIDAuMDIpLnNsb3coMTYpKQogICAgLmNob3J1cyguNSkKICAgIC5yb29tKC43KS5zaXplKC45NSkKICAgIC5kZXR1bmUoIjAsMC4xIikKICAgIC5kZWxheSgiMC44IikKICAgIC5zbG93KDgpCiAgICAuX3B1bmNoY2FyZCgpCgpsZXQgd2F2ZXMgPSBzdGFjaygKICBub2lzZSgwLjcpCiAgICAuc291bmQoInBpbmsiKQogICAgLmhwZig0MCkubHBmKDQwMCkKICAgIC5nYWluKHNpbmUucmFuZ2UoLjQsLjYpLnNsb3coMTYpKQogICAgLnJvb20oLjgpLnNpemUoMSksCgogIG5vaXNlKDAuNykKICAgIC5zb3VuZCgicGluayIpCiAgICAuaHBmKDQwMCkubHBmKDMwMDApCiAgICAuZ2FpbihzaW5lLnJhbmdlKC4yLC4zKS5zbG93KDEyKSkKICAgIC5wYW4oc2luZS5yYW5nZSgwLDEpLnNsb3coMjQpKQogICAgLnJvb20oLjg1KS5zaXplKC45NSksCiAgCiAgbm9pc2UoMC43KQogICAgLnNvdW5kKCJwaW5rIikKICAgIC5ocGYoMzAwMCkubHBmKDkwMDApCiAgICAuZ2FpbihzaW5lLnJhbmdlKC4xLC4yKS5zbG93KDEyKSkKICAgIC5wYW4ocGVybGluLnJhbmdlKDAsMSkuc2xvdygxOCkpCiAgICAucm9vbSguOSkuc2l6ZSgxKQogICAgLmRlZ3JhZGVCeSguMSksCiAgCiAgbm9pc2UoMC43KQogICAgLnNvdW5kKCJwaW5rIikKICAgIC5ocGYoNjAwKS5scGYoNDAwMCkKICAgIC5hZHNyKCIuMjowLjU6MDoxIikKICAgIC5nYWluKC4yNSkKICAgIC5zdHJ1Y3QoInggfiB%2BIH4iKQogICAgLnBseSg4KQogICAgLnBhbihyYW5kKQogICAgLnNsb3coOC4zKQopLmRlbGF5KDAuNykuZ2FpbihzaW5lLnJhbmdlKDAuMTUsIDAuMjUpLnNsb3coOCkpLl9wdW5jaGNhcmQoKQoKbGV0IGJhc2UgPSBub3RlKCJjMiIpLnNvdW5kKCJzaW5lIikKICAuYWRzcigiNDoxMjouNTo4IikKICAuZ2FpbiguOCkKICAucm9vbSguOCkuc2l6ZSguOSkKICAuc2xvdyg4KQogIC5fcHVuY2hjYXJkKCkKCmxldCBjaGltZSA9IG4oIjAgNCA3IDExIikuc2NhbGUoIkM6bWlub3IiKQogIC5zb3VuZCgidHJpYW5nbGUiKQogIC5hZHNyKCIwLjAxOjI6Ljc6NSIpCiAgLmRlbGF5KCIuMzouMzU6LjYiKQogIC5nYWluKC4xNSkKICAuc29tZXRpbWVzQnkoLjcsIHggPT4geC5zdHJ1Y3QoIn4iKSkgIC8vIHBsYXkgbGVzcyBvZnRlbgogIC5zbG93KDQpCiAgLl9wdW5jaGNhcmQoKQoKbGV0IGRydW0gPSBzKCJiZCB%2BIH4gfiIpLmJhbmsoIlJvbGFuZFRSOTA5IikKICAuZ2FpbiguMikKICAucm9vbSguNykuc2l6ZSguOSkKICAuZGVncmFkZUJ5KC40KQogIC5zbG93KDIpCiAgLmRlbGF5KCIwLjY1OjE6MSIpCiAgLl9wdW5jaGNhcmQoKQoKJDogYXJyYW5nZSgKICBbNCwgYXJwXSwKICBbOCwgc3RhY2soYXJwLCB3YXZlcywgYmFzZSwgZHJ1bSldLAogIFsxNiwgc3RhY2soYXJwLCB3YXZlcywgYmFzZSwgZHJ1bSwgcGFkLCBjaGltZSldLAogIFs4LCBzdGFjayhhcnAyLCB3YXZlcywgYmFzZSwgZHJ1bSwgcGFkLCBjaGltZSldLAogIFsxNiwgc3RhY2soYXJwLCB3YXZlcywgYmFzZSwgZHJ1bSwgcGFkLCBjaGltZSldLAogIFs4LCBzdGFjayh3YXZlcywgYmFzZSwgZHJ1bSldLAogIFs0LCBzdGFjayh3YXZlcyldLAopCgokOiBscGYoc2luZS5yYW5nZSg5MDAsIDI4MDApLnNsb3coMTYpKQ%3D%3D"
  width="600"
  height="300"
&gt;&lt;/iframe&gt;

&lt;p&gt;What an incredible tool. Mind blowing.&lt;/p&gt;</description>
    </item>

    <item>
      <title>New page: [/ai](https://nathanherald.com/ai)</title>
      <link>https://nathanherald.com/ai/</link>
      <pubDate>Tue, 19 Aug 2025 20:25:08 +0200</pubDate>
      <guid isPermaLink="true">https://nathanherald.com/ai/</guid>
      <description>&lt;h1 id="new-page-ai"&gt;New page: &lt;a
href="https://nathanherald.com/ai"&gt;/ai&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I want to publish some thoughts about AI on my website, but I don't
want those thoughts scattered over many blog posts. So I've made a new
page at &lt;a href="https://nathanherald.com/ai"&gt;/ai&lt;/a&gt; to hold those
thoughts. It will be a living document, I'll update it whenever I can,
and if anything is super interesting then I'll throw another RSS item in
to let you know. 🫡&lt;/p&gt;</description>
    </item>

    <item>
      <title>Removing Hugo</title>
      <link>https://nathanherald.com/posts/removing-hugo/</link>
      <pubDate>2025-03-09T22:14:08+01:00</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/removing-hugo/</guid>
      <description>
        &lt;p&gt;
          &lt;strong&gt;A new experiment:&lt;/strong&gt; I have completely removed my build tool. It’s just me and HTML now.
        &lt;/p&gt;
        &lt;p&gt;
          I‘ve had an itch for a while to dump hugo and just go straight html for a while… and I've finally done it. You can see &lt;a href="https://github.com/myobie/nathanherald.com/tree/main/public"&gt;in the repo for this website&lt;/a&gt;.
        &lt;/p&gt;
        &lt;p&gt;
          No more hugo, no more build tool, no more having to deal with new versions, deprecations, weird shortcodes, weird markdown things…
        &lt;/p&gt;
        &lt;p&gt;
          &lt;a href="#"&gt;I created this page in a PR,&lt;/a&gt; added it to the &lt;a href="/posts"&gt;/posts&lt;/a&gt; index, to the homepage recent links, and to the &lt;a href="/rss.xml"&gt;rss feed.&lt;/a&gt;
        &lt;/p&gt;
        &lt;p&gt;
          My intention is to build up some custom elements to help build new pages more quickly. We‘ll see how that goes. For now, this isn't too bad.
        &lt;/p&gt;
      </description>
    </item>
    
    <item>
      <title>Before the BF, there was the T – my first mirrorless camera 🖤</title>
      <link>https://nathanherald.com/posts/before-the-bf-there-was-the-t/</link>
      <pubDate>Fri, 28 Feb 2025 01:02:20 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/before-the-bf-there-was-the-t/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;My Leica T with its 23mm Summicron lens&#34;
    width=&#34;1024&#34;
    height=&#34;656&#34;
    src=&#34;https://nathanherald.com/posts/before-the-bf-there-was-the-t/t_hu12252918283310764783.jpg&#34;&gt;
&lt;/figure&gt;

&lt;hr&gt;
&lt;p&gt;An all aluminum body, milled from a single block, and polished for hours. With a minimal UI + big screen, people say it’s the camera for those who love their smartphone.&lt;/p&gt;
&lt;p&gt;Yes, that was the &lt;a href=&#34;https://en.wikipedia.org/wiki/Leica_T_(Typ_701)&#34;&gt;Leica T&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/8zmy8Amb9ro&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;hr&gt;
&lt;p&gt;A couple years ago I wanted to get into photography and get a &lt;em&gt;real camera&lt;/em&gt;, so I got an old, original T. And I couldn’t be happier with it. Its design was ahead of its time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Aside: &lt;a href=&#34;https://used-photo.de&#34;&gt;used-photo.de&lt;/a&gt; is great. If you are looking for a used Leica, check them out. They were very helpful and even helped me get both the T body and my first lens, the 23mm Summicron. I couldn’t recommend them enough.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Aside 2: I sent the T to Wetzlar to clean and just check over everything. You can ship any camera back to Leica and they will do their best. And they were great. They replaced the motherboard in the camera, which did fix some problems and made it operate faster. And they completely replaced my lens because the electronics didn’t fully pass their tests. My original 23mm lens was “T mount” as it was initially called. This new one is “L mount” as they call it now. The cost was incredibly low for this service. I have been impressed by Leica as a company.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;And now, Sigma have announced the &lt;a href=&#34;https://www.sigma-global.com/en/cameras/bf/&#34;&gt;BF&lt;/a&gt;. And it definitely feels like the spiritual successor to the T. The design from Leica was ahead of its time.&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://www.sigma-global.com/en/cameras/bf/&#34;&gt;
  &lt;img
    alt=&#34;The new Sigma BF&#34;
    width=&#34;1200&#34;
    height=&#34;800&#34;
    src=&#34;https://nathanherald.com/posts/before-the-bf-there-was-the-t/bf_hu5620634592478640410.jpg&#34;&gt;
  &lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;em&gt;(Leica made a TL and then a TL2, but the vibe and design was unchanged + I own the original so that’s what I know best. They recently stopped making the TL2, so there definitely is a gap in the market for this type of camera now.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The new Sigma BF is both extremely similar in vibe + better in every way. It even uses the same L mount! My lenses would work on it, and the new Sigma lenses would work on my T.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;T&lt;/th&gt;
          &lt;th&gt;BF&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;APSC&lt;/td&gt;
          &lt;td&gt;Full frame&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Bad autofocus&lt;/td&gt;
          &lt;td&gt;Good autofocus&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;18MP&lt;/td&gt;
          &lt;td&gt;24MP&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;L mount&lt;/td&gt;
          &lt;td&gt;L mount&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Hot shoe&lt;/td&gt;
          &lt;td&gt;No shoe&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;🤔&lt;/p&gt;
&lt;p&gt;I have been thinking for a while that I should get one of the last TL2 bodies so I could get the improved auto-focus, focus peeking zebras, and 24MP… and now I’m thinking I should save up a little longer and end up with a T and a BF on my shelf 💸&lt;/p&gt;
&lt;p&gt;My T takes great photos. I mostly do black and white, but any old Leica can take great photos of any kind of scene. From detailed architecture, to soft morning light in the green forest, to the soft coastal blues, and really anything else.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Four of my photos, one black and white of a building, one of the morning light in a forest, one of a hotel facade at night, and one of the coast of Portugal&#34;
    width=&#34;1200&#34;
    height=&#34;1106&#34;
    src=&#34;https://nathanherald.com/posts/before-the-bf-there-was-the-t/collage_hu3737047065016868914.jpg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;I’ve learned the basics of the exposure triangle with it. It taught me photography.&lt;/p&gt;
&lt;p&gt;I mostly shoot with manual focus and I lock to a specific ISO, so I am adjusting aperture and shutter speed with the two wheels on top of the T. &lt;em&gt;(I mostly do this because the autofocus is not good on the old T. But I also do it to build up the muscle memory to intuit the exposure of different scenes for when in shoot with my antique film cameras.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The BF only has one wheel, on the back, &lt;em&gt;but&lt;/em&gt; the sigma lenses have an aperture ring. So it really has the same two wheels I am used to using.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Leica L mount lenses don’t have an aperture ring, it’s driven electronically.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So, the Sigma BF is very appealing to me. The upgraded video capabilities would be great, keeping the L mount is great, and moving up to a full frame sensor would make a difference to me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Both lack a view finder.&lt;/strong&gt; And a lot of people seem to think they could never take photos without one. I can say confidently that using the screen can be a great experience. Having the camera out in front like a phone or a compact like the Ricoh GR III brings less attention while out on the street. Pulling a camera up to my eye can sometimes turn heads when I only meant to observe.&lt;/p&gt;
&lt;p&gt;And that is one advantage the T (or TL2) has: &lt;strong&gt;an EVF can be affixed to the hot shoe.&lt;/strong&gt; The BF doesn’t have a hot shoe, so it’s screen for life.&lt;/p&gt;
&lt;p&gt;Watching/reading the reviews: the BF almost seems like it was made for me. I love my T, but I wish it had great autofocus, higher MP, etc.&lt;/p&gt;
&lt;p&gt;Shooting with a minimal camera like the T is really freeing. The simplicity of the UI keeps me from getting distracted or diving into menus or any of that. I set it up how I want, then I go out and enjoy shooting. And I would wager the BF will be the same.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you’ve never shot with a minimal, stripped down camera like these then I recommend you borrow one.&lt;/strong&gt; Having almost no buttons forces one to focus on the composition. There is nothing available to waste time with 😅&lt;/p&gt;
&lt;p&gt;It’s excited to see more “weird” cameras come out. So much of what’s available all look and operate the same, with very minute details. Let’s see how long it takes me to save up for a BF.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Reactive DOM nodes &#43; reactive templates for custom elements (web components)</title>
      <link>https://nathanherald.com/posts/reactive-dom-nodes-and-templates-for-custom-elements/</link>
      <pubDate>Sun, 08 Dec 2024 11:15:20 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/reactive-dom-nodes-and-templates-for-custom-elements/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/preactjs/signals&#34;&gt;Signals&lt;/a&gt; are now my go-to tool to model my reactive state for my apps in the browser. However, the hard part, for me, has been figuring out how I would prefer to bring that reactivity to the DOM. I think I’ve finally landed on a solution that I like using &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;For an explainer on &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;, &lt;a href=&#34;https://nathanherald.com/posts/building-templates/&#34;&gt;checkout my previous post abut building templates for custom elements&lt;/a&gt;. This post builds on that one.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;bring-the-signal-to-the-dom&#34;&gt;Bring the signal to the DOM&lt;/h2&gt;
&lt;p&gt;The basic way to know when a signal is updated is to &lt;code&gt;subscrbe()&lt;/code&gt; to it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;subscribe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;newNumber&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;new number!&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;newNumber&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// log: new number! 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// log: new number! 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One can also use an effect to achieve the same thing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;effect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// using number.value will auto-subscribe this effect to number
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;new number!&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// log: new number! 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// log: new number! 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;dispose&#34;&gt;Dispose&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Both &lt;code&gt;subscribe()&lt;/code&gt; and &lt;code&gt;effect()&lt;/code&gt; return a “disposal function” which “turns them off”… basically they stop watching / tracking any signals. You have to manually dispose of these subscriptions. It is annoying to me that one cannot provide an &lt;code&gt;AbortSignal&lt;/code&gt; to the subscription 😠 Either way, this is what you have to do:&lt;/em&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;dispose&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;effect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;yay&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// log: yay 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dispose&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// no log
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;text&#34;&gt;&lt;code&gt;Text&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The easiest way to “bind” a signal to the DOM is to update a &lt;code&gt;Text&lt;/code&gt; node anytime a signal’s value changes. Assuming the signal (or computed) contains a string, this would work:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;reactiveTextNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createTextNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;effect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;textContent&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that’s it. Now we have a &lt;code&gt;Text&lt;/code&gt; node that can be inserted anywhere in the DOM + it will update anytime it’s signal is updated 💪&lt;/p&gt;
&lt;p&gt;Try this in your browser console for a full example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// paste this on it&amp;#39;s own line by itself or else safari will complain
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;preactSignals&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;https://esm.sh/@preact/signals-core&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// then paste all this at once
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;computed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;effect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;preactSignals&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;reactiveTextNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createTextNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;effect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;textContent&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;James&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;hello&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;computed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`Hello &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;reactiveTextNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hello&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// and now you can update the name, which will update the DOM
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Alice&amp;#39;&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// updates!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Bob&amp;#39;&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// updates again!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;inserting-signals-into-a-template&#34;&gt;Inserting signals into a template&lt;/h2&gt;
&lt;p&gt;I’d like to be able to directly interpolate a signal like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;  &amp;lt;p&amp;gt;Hello &amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And it would auto-create the reactive &lt;code&gt;Text&lt;/code&gt; node.&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&#34;https://nathanherald.com/posts/building-templates/&#34;&gt;previous post&lt;/a&gt; I made it possible to nest template by looking through all the interpolated objects looking for a template object, and using a small custom element as a stand-in inside the template’s content.&lt;/p&gt;
&lt;p&gt;I’ll do the same for a signal inside my &lt;code&gt;Template&lt;/code&gt; class:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// when pre-processing the values in the template string...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;instanceof&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;signals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`&amp;lt;s- data-i=&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&amp;#34;&amp;gt;&amp;lt;/s-&amp;gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// then later in cloneNode()...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;signals&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;s-&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// for each custom element, replace it with a reactive text node
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;signals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;forEach&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dataset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sig&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;signals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;replaceWith&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;reactiveTextNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This would take care of it. Don’t worry if this feels out of place, it is. &lt;em&gt;Checkout &lt;a href=&#34;https://codepen.io/myobie/pen/qEWZYYv?editors=0010&#34;&gt;this new codepen for a full example with an updated &lt;code&gt;Template&lt;/code&gt; class and everything else I’m about to write about below&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;updating-the-signal-in-response-to-user-input&#34;&gt;Updating the signal in response to user input&lt;/h3&gt;
&lt;p&gt;I’d like to be able to update the name, so I can see it &lt;em&gt;react&lt;/em&gt;. So I’m going to expand the template to include an input and a custom element which will listen for &lt;code&gt;input&lt;/code&gt; events and update the signal in response:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;  &amp;lt;update-name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;    &amp;lt;input value=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&amp;gt;&amp;lt;br&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;    &amp;lt;p&amp;gt;Hello &amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;  &amp;lt;/update-name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the custom element class:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;UpdateName&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;extends&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;HTMLElement&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;constructor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;super&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;display&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;contents&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;handleEvent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;target&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;connectedCallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addEventListener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;input&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;disconnectedCallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;removeEventListener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;input&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;customElements&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;update-name&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;UpdateName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And this now is a complete app. I’ll embed the codepen right here so you can try it out and explore the full source altogether:&lt;/p&gt;

&lt;p class=&#34;codepen&#34; data-height=&#34;300&#34; data-default-tab=&#34;html,result&#34; data-slug-hash=&#34;qEWZYYv&#34; data-pen-title=&#34;Reactive DOM nodes + reactive templates for custom elements (web components)&#34; data-user=&#34;myobie&#34; style=&#34;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&#34;&gt;
  &lt;span&gt;See the Pen &lt;a href=&#34;https://codepen.io/myobie/pen/qEWZYYv&#34;&gt;
  Reactive DOM nodes + reactive templates for custom elements (web components)&lt;/a&gt; by Nathan Herald (&lt;a href=&#34;https://codepen.io/myobie&#34;&gt;@myobie&lt;/a&gt;)
  on &lt;a href=&#34;https://codepen.io&#34;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;script async src=&#34;https://cpwebassets.codepen.io/assets/embed/ei.js&#34;&gt;&lt;/script&gt;


&lt;hr&gt;
&lt;h2 id=&#34;alternate-ways-to-build-reactive-templates&#34;&gt;Alternate ways to build reactive templates&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://indieweb.social/@mb21@hachyderm.io&#34;&gt;mb21&lt;/a&gt; &lt;a href=&#34;https://indieweb.social/@mb21@hachyderm.io/113616192332556127&#34;&gt;replied&lt;/a&gt; to my &lt;a href=&#34;https://nathanherald.com/posts/building-templates/&#34;&gt;previous post&lt;/a&gt; with a link to &lt;a href=&#34;https://github.com/mb21/mastro/tree/main/src/reactive#reactive-mastro&#34;&gt;github.com/mb21/mastro/tree/main/src/reactive#reactive-mastro&lt;/a&gt; which is a very cool way to handle this.&lt;/p&gt;
&lt;p&gt;What I like about it is it shows how you can very quickly &lt;em&gt;hydrate&lt;/em&gt; server-side generated markup using their custom element subclass.&lt;/p&gt;
&lt;p&gt;I, personally, am not a fan of putting things like &lt;em&gt;bindings&lt;/em&gt; or &lt;em&gt;handlers&lt;/em&gt; in the html directly. In my journey I’ve arrived at the above &lt;em&gt;reactive text node&lt;/em&gt; primitive from which I can build up fairly complex web apps. And DOM elements already bubble up their events, so if I want to handle a click I’ll create a custom element like I showed above to listen for and handle it. I prefer that style.&lt;/p&gt;
&lt;p&gt;And this is why the web is great: the tools we have in the browser are incredibly flexible, there isn’t a right way to use them.&lt;/p&gt;
&lt;p&gt;If it works, if it meets your customers / readers / viewers needs, if it’s as standard and accessible as it can be, then you did it correctly 🫡&lt;/p&gt;
&lt;h2 id=&#34;open-questions&#34;&gt;Open questions&lt;/h2&gt;
&lt;p&gt;While this is great to see how quickly one can put together some reactive templates, there are still a lot of questions to answer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What about a reactive list? (oh don’t get me started 😵‍💫)&lt;/li&gt;
&lt;li&gt;What about template slots? (sure)&lt;/li&gt;
&lt;li&gt;What about server-side rendering? (a very big topic indeed)&lt;/li&gt;
&lt;li&gt;Isn’t this making a template for every single DOM node? (it’s worse than you think)&lt;/li&gt;
&lt;li&gt;Shadow DOM? (they who shall not be named)&lt;/li&gt;
&lt;li&gt;What about effect disposal? (yeah, it’s not easy)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And I’m sure many more. What questions do you have about custom elements that haven’t been answered yet?&lt;/p&gt;
&lt;p&gt;I am on a journey to answer these questions, and more, for myself. I think we can build highly-sophisticated web apps with only custom elements today, and I’m going to learn how.&lt;/p&gt;
&lt;p&gt;Find me on &lt;a href=&#34;https://indieweb.social/@myobie&#34;&gt;mastodon&lt;/a&gt; and let me know what you think.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Building templates for custom elements (web components)</title>
      <link>https://nathanherald.com/posts/building-templates/</link>
      <pubDate>Sat, 07 Dec 2024 21:51:46 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/building-templates/</guid>
      <description>&lt;h2 id=&#34;what-is-template&#34;&gt;What is &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;If you, like me, have ever tried to use it for “templating” then you’ve probably also been frustrated. I’ve now used &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; enough that I think I get it, I think I understand what it’s good for. And it’s not templating.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template&#34;&gt;&lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; is for holding document fragments.&lt;/a&gt; That’s it.&lt;/p&gt;
&lt;p&gt;Those DOM fragments are reusable. They expose a &lt;code&gt;cloneNode()&lt;/code&gt; function which allows one to duplicate it, to then be inserted somewhere. The template’s content doesn’t change, &lt;code&gt;cloneNode()&lt;/code&gt; just makes a duplicate.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createElement&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;template&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;innerHTML&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Hello&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;frag&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cloneNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// true means deep clone, you almost always want this
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;frag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// appending a fragment will move all of its children into the target element
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The above doesn’t feel like what I would call “a template.”&lt;/p&gt;
&lt;p&gt;If you needed 10 &lt;code&gt;&amp;lt;p&amp;gt;A&amp;lt;/p&amp;gt;&lt;/code&gt;s all the same, then this would work great. But most of the time I have some &lt;em&gt;shared markup&lt;/em&gt; that I want to reuse, like for building up a list of grocery items for example. I might want to share something like &lt;code&gt;&amp;lt;li&amp;gt;${name}&amp;lt;/li&amp;gt;&lt;/code&gt; and then reuse that over and over for each grocery item.&lt;/p&gt;
&lt;p&gt;This is a way to make the grocery list using &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;groceryItems&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;milk&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;eggs&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;eggos&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ul&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createElement&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;ul&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createElement&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;template&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;innerHTML&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;of&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;groceryItems&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;frag&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cloneNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;frag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;li&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;innerHTML&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;ul&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;frag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ul&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This feels like a lot to just place the grocery item’s name into the &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; for each.&lt;/p&gt;
&lt;p&gt;Most of us are used to writing something like this in JSX:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-jsx&#34; data-lang=&#34;jsx&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;ul&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;groceryItems&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;li&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;li&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;))}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;ul&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;JSX is not available in the browser tho. It requires a build system… and it’s XML and not HTML. &lt;em&gt;And&lt;/em&gt; if you’ve ever had to put in a &lt;code&gt;{&#39; &#39;}&lt;/code&gt;, well, there are edge cases 😉&lt;/p&gt;
&lt;p&gt;What we do have in the browser are &lt;em&gt;template strings.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;template-strings&#34;&gt;Template strings&lt;/h2&gt;
&lt;p&gt;Unrelated to &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;, template strings are those backtick multi-line strings that can interpolate stuff. Like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;say&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;what&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;warn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;what&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;say&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`Hello &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt; Again`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// =&amp;gt; &amp;#39;Hello 5 Again&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There is another way to call a function with a template string though:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;say&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`Hello &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt; Again`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// =&amp;gt; [&amp;#34;Hello &amp;#34;, &amp;#34; Again&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// =&amp;gt; 5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that is incredibly different. By removing the parentheses and putting the backticks up against the function, it calls it &lt;strong&gt;before&lt;/strong&gt; interpolating the number.&lt;/p&gt;
&lt;p&gt;You are expected to zip the first array with all the following arguments. If you look at the above example, you can see how first comes a string, then the five, then another string, and then we are done.&lt;/p&gt;
&lt;p&gt;Implementing that type of zipping of arrays is annoying… and luckily there is a built in function that will do it for you: &lt;code&gt;String.raw()&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;say&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;message&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;raw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;raw&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;warn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;say&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`Hello &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt; Again`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// =&amp;gt; &amp;#39;Hello 5 Again&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let’s make a sweet &lt;code&gt;html&lt;/code&gt; function that can take a template string and return an &lt;code&gt;HTMLTemplateElement&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createElement&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;template&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;source&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;raw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;raw&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;innerHTML&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;source&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Milk&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;  &amp;lt;ul&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;    &amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&amp;lt;/li&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;  &amp;lt;/ul&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which is super cool. That already looks &lt;em&gt;more templatey&lt;/em&gt; to me. There is still some work to do to though be able to interpolate the entire grocery list, so let’s get to it.&lt;/p&gt;
&lt;p&gt;I feel like I need to be able to nest templates to build the full grocery list… so I need to pre-process &lt;code&gt;args&lt;/code&gt; before passing it to &lt;code&gt;String.raw()&lt;/code&gt; to deal with any interpolated templates.&lt;/p&gt;
&lt;p&gt;To make this easier, I’d like to package this up into my own class so I can keep some state and more easily detect a nested template.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Template&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;err&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;embeddedTemplates&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;constructor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;source&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;element&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createElement&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;template&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// pre-process the template string values
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;updatedValues&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;prepareValues&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// zip
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;source&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;raw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;raw&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;source&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;updatedValues&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// assign as the source
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;element&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;innerHTML&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;source&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;err&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;prepareValues&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;index&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;prepareValue&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;index&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;index&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;typeof&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;boolean&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;null&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;undefined&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// if someone interpolates nothing, return an empty string and move on
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;instanceof&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// if we find a template, store it in our Map
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;        &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;embeddedTemplates&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// substitute a small custom element including the index in the Map for this embedded template
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;        &lt;span class=&#34;c1&#34;&gt;// our template content must be a string, so we are using this little custom element to stand in for our non-string Template object
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`&amp;lt;t- data-i=&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&amp;#34;&amp;gt;&amp;lt;/t-&amp;gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;values&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;c1&#34;&gt;// just in case someone interpolated an array, then let&amp;#39;s map over it
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;      &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isArray&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;prepareValue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;prepareValue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// we&amp;#39;ll just always imagine it&amp;#39;s true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;nx&#34;&gt;cloneNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;element&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cloneNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// find all those custom elements that are standing in for embedded template objects
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;embeddedTemplates&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;t-&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// for each custom element, replace it with the cloned fragment of the template object it&amp;#39;s standing in for
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;nx&#34;&gt;embeddedTemplates&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;forEach&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dataset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;embeddedTemplates&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;replaceWith&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cloneNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I know that is a lot. I added comments to the code and hopefully that helps a bit.&lt;/p&gt;
&lt;p&gt;With that class, I can now write:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;  &amp;lt;ul&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;groceryItems&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;      &amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&amp;lt;/li&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;    `&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;  &amp;lt;/ul&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cloneNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is looking very &lt;em&gt;templatey.&lt;/em&gt; Hopefully you can see the nested &lt;code&gt;html&lt;/code&gt; call in there.&lt;/p&gt;
&lt;p&gt;This should give us the html we expect.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://codepen.io/myobie/pen/WbewXxB?editors=0010&#34;&gt;This codepen has a working example&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The resulting DOM list isn’t reactive. If I want to add a new item to the grocery list, it won’t update. And that is a bigger topic for another post, hopefully soon.&lt;/p&gt;
&lt;p&gt;Hit me up on &lt;a href=&#34;https://indieweb.social/@myobie&#34;&gt;mastodon&lt;/a&gt; and let me know what you think of my &lt;code&gt;Template&lt;/code&gt; class, how are you handling templating with custom elements, or anything else 🫡&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>How to build custom elements that work no matter what order things are loaded</title>
      <link>https://nathanherald.com/posts/how-to-build-custom-elements-that-work-no-matter-what-order-things-are-loaded/</link>
      <pubDate>Tue, 26 Nov 2024 19:28:55 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/how-to-build-custom-elements-that-work-no-matter-what-order-things-are-loaded/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TL;DR:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Put all the important code in &lt;code&gt;connectedCallback&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Either don’t depend on any specific DOM parent being pre-defined, or &lt;code&gt;await customElements.whenDefined(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Cleanup any listeners, etc in the &lt;code&gt;disconnectedCallback&lt;/code&gt; for good hygiene&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Custom elements are seemingly created in three steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Subclass HTMLElement&lt;/li&gt;
&lt;li&gt;Define the tag name&lt;/li&gt;
&lt;li&gt;Element is constructed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Which seems simple and complete, but it turns out things can happen in a lot of different orders and aren’t nearly that simple.&lt;/p&gt;
&lt;p&gt;I’ve done some testing for different scenarios to see when are an elements attributes set, when are the element’s children reachable, and when is the element’s parent reachable? I’m trying to document what I’ve learned below.&lt;/p&gt;
&lt;p&gt;Example html I will use to work through this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;one-&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;two-&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;two-&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;one-&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;one-&amp;gt;&lt;/code&gt; is a valid custom element name. The only rules are: 1) must start with a letter and 2) must have at least one hyphen (-). &lt;code&gt;&amp;lt;t-&amp;gt;&lt;/code&gt; is a valid custom element name, for example.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Also, just in case you don’t know, a custom element’s class can have a &lt;code&gt;constructor&lt;/code&gt; and it can have a &lt;code&gt;connectedCallback&lt;/code&gt; method which will be called when the element is attached to a document. It’s similar to “on mount” in the react and related world.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are a few scenarios or orders that custom elements might be defined and discovered:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A tag’s name might be in the HTML returned from the server, but the subclass might not be defined yet in the custom elements registry&lt;/li&gt;
&lt;li&gt;A tag name might show up in the html only after the subclass is defined&lt;/li&gt;
&lt;li&gt;The element class might be constructed in javascript, unrelated to html parsing&lt;/li&gt;
&lt;li&gt;Elements might be defined in a non-deterministic order&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;a-tags-name-might-be-in-the-html-returned-from-the-server-but-the-subclass-might-not-be-defined-yet-in-the-custom-elements-registry&#34;&gt;A tag’s name might be in the HTML returned from the server, but the subclass might not be defined yet in the custom elements registry&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;If you normally put your &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;s at the bottom of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, the this is your scenario.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The HTML parser will first construct a generic &lt;code&gt;HTMLElement&lt;/code&gt; since the tag’s name isn’t in the &lt;code&gt;customElements&lt;/code&gt; registry. The element is style-able by CSS and the CSS can even see if the element is &lt;code&gt;:defined&lt;/code&gt; or not (if it’s in the &lt;code&gt;customElements&lt;/code&gt; registry). This can cause a flash of undefined custom elements depending on how you implement the CSS and HTML for the component. Some devs choose to hide &lt;code&gt;not(:defined)&lt;/code&gt; elements to avoid this.&lt;/p&gt;
&lt;p&gt;After some Javascript is loaded and defines the tag name to be a subclass of HTMLElement, the generic HTMLElement will automatically be upgraded and replaced. &lt;code&gt;customElements.define(&#39;one-&#39;, OneElement)&lt;/code&gt; will cause the DOM to look for any existing &lt;code&gt;one-&lt;/code&gt; elements and upgrade them right then.&lt;/p&gt;
&lt;p&gt;When an element is upgraded from a generic one to a custom one, the elements &lt;code&gt;children&lt;/code&gt; are accessible in the class’s &lt;code&gt;constructor&lt;/code&gt;, &lt;code&gt;connectedCallback&lt;/code&gt;, etc – it’s a leaf-first situation. So &lt;code&gt;this.children&lt;/code&gt; will be accurate anywhere, anytime for this scenario.&lt;/p&gt;
&lt;p&gt;This is not the end of the story, though. Because of how HTML is parsed and because scripts are run inline, it’s possible that there is more HTML to go through after the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; which defined &lt;code&gt;OneElement&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As the HTML parser encounters new &lt;code&gt;&amp;lt;one-&amp;gt;&lt;/code&gt; elements, it will directly instantiate the &lt;code&gt;OneElement&lt;/code&gt; class right then. So half the page might work one way, the other half might work another way. &lt;em&gt;(I recommend never letting this happen, it’s just confusing 😅)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is identical to the next scenario…&lt;/p&gt;
&lt;h2 id=&#34;a-tag-name-might-show-up-in-the-html-only-after-the-subclass-is-defined&#34;&gt;A tag name might show up in the html only after the subclass is defined&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;If you put your &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;s in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; and you do not use &lt;code&gt;defer&lt;/code&gt;, then this is your scenario.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Since the &lt;code&gt;OneElement&lt;/code&gt; is already registered into &lt;code&gt;customElement&lt;/code&gt;, the HTML parser will directly instantiate a new &lt;code&gt;OneElement&lt;/code&gt; for each &lt;code&gt;&amp;lt;one-&amp;gt;&lt;/code&gt; it encounters. There won’t be a time where the CSS selector &lt;code&gt;not(:defined)&lt;/code&gt; would match, there is not &lt;em&gt;upgrade&lt;/em&gt;, and so there is not flash of undefined custom elements.&lt;/p&gt;
&lt;p&gt;And the access to &lt;code&gt;this.children&lt;/code&gt; is the same; whether your code is in the &lt;code&gt;constructor&lt;/code&gt;, &lt;code&gt;connectedCallback&lt;/code&gt;, or anywhere else, &lt;code&gt;this.children&lt;/code&gt; should be what you expect.&lt;/p&gt;
&lt;p&gt;However, there is one more scenario that isn’t related to HTML parsing…&lt;/p&gt;
&lt;h2 id=&#34;the-element-class-might-be-constructed-in-javascript-unrelated-to-html-parsing&#34;&gt;The element class might be constructed in javascript, unrelated to html parsing&lt;/h2&gt;
&lt;p&gt;I’m sure you’ve had to write code like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;div&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createElement&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;div&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;div&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;id&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;some-id&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;paragraph&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createElement&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;p&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;paragraph&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;innerHTML&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Hello 👋&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;div&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;paragraph&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// etc, etc... 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;div&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And it feels very tedious… well this can happen for custom elements as well.&lt;/p&gt;
&lt;p&gt;One can use &lt;code&gt;document.createElement(&#39;one-&#39;)&lt;/code&gt; or &lt;code&gt;new OneElement()&lt;/code&gt; to make a new instance of the custom element… and it doesn’t have its children yet.&lt;/p&gt;
&lt;p&gt;So in this scenario, it is not safe to look at &lt;code&gt;this.children&lt;/code&gt; or &lt;code&gt;this.parentNode&lt;/code&gt; in the &lt;code&gt;constructor&lt;/code&gt;… It also doesn’t have any of it’s attributes set… so you can’t really look for those attributes in the &lt;code&gt;constructor&lt;/code&gt; either… which can be really annoying.&lt;/p&gt;
&lt;p&gt;However, there is good news, you can trust &lt;code&gt;this.children&lt;/code&gt; and all the other stuff in the &lt;code&gt;connectedCallback&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Try this to prove it to yourself (just try this in the console at any URL):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;A&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;extends&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;HTMLElement&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;connectedCallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;connected!&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// if you try `new A()` right now, you will get an error. A custom element can be instantiated until it is defined in the registry.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;customElements&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;a-&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// yay!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;div&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createElement&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;div&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// luckily, this does not call the connectedCallback yet!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;div&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;div&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// &amp;#34;connected!&amp;#34; will be logged, the connectedCallback was finally called
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As long as custom elements are instantiated, fully prepared, and then added to a document things will probably work out just fine for any code in the &lt;code&gt;connectedCallback&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One way I work around this sometimes is to accept attributes and children as optional arguments to the constructor, like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;FancyElement&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;extends&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;HTMLElement&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;constructor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;attributes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;children&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;super&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;attributes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;of&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;entries&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;attributes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;setAttribute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;children&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;replaceChildren&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;children&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// OH yeah!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So now I can safely put setup code in the constructor, as long as every dev knows this is how this custom element needs to be used… I don’t do this often, but sometimes this is the easiest way to move forward. &lt;em&gt;Really tho, just try to only ever put important code in the &lt;code&gt;connectedCallback&lt;/code&gt; if at all possible.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is all fine for one custom element, but what if there are many custom elements and they are all defined at different times 🤔&lt;/p&gt;
&lt;h2 id=&#34;elements-might-be-defined-in-a-non-deterministic-order&#34;&gt;Elements might be defined in a non-deterministic order&lt;/h2&gt;
&lt;p&gt;Using the same example from above, assuming none of the subclasses have been defined during HTML parsing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;one-&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;two-&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;two-&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;one-&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The order the subclasses are defined really matters. If, for some reason, OneElement was going to do something that TwoElement depends on (like setup a database or something), then you better make sure to define OneElement first.&lt;/p&gt;
&lt;p&gt;Having a direct dependency on a parent in the DOM is an anti-pattern, try not to do it unless absolutely necessary.&lt;/p&gt;
&lt;p&gt;And, well, if you really want to make sure, then you can wait for the other custom element to be defined inside the connected callback:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;TwoElement&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;extends&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;HTMLElement&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;customElements&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;two-&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;connectedCallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;customElements&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;whenDefined&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;one-&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// now the parent is fully there 💪
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This might look like it could end up in a deadlock if two tags were waiting on each other, but it won’t. connectedCallback() is not actually async and the DOM runtime will not await the promise this example returns, so things can work out concurrently just fine.&lt;/p&gt;
&lt;p&gt;(You can also use this trick the other way: wait until you know all the custom elements that might be children are defined.)&lt;/p&gt;
&lt;p&gt;Still, you can also just make sure things are defined in an outside-in order in your code. That is what I prefer since it’s less code for me to write and I need this rarely.&lt;/p&gt;
&lt;h2 id=&#34;is-this-all-a-mess&#34;&gt;Is this all a mess?&lt;/h2&gt;
&lt;p&gt;Kinda, but I’m OK with it. Custom elements are, IMHO, not a easy as react-like components because they are alive, living in the DOM, and dealing with the real built-in HTML parser and renderer which have to deal with a lot more than just JSX… If you embrace a few conventions, then it normally all works out fine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Put all the important code in &lt;code&gt;connectedCallback&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Either don’t depend on any specific DOM parent being pre-defined, or &lt;code&gt;await customElements.whenDefined(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Cleanup any listeners, etc in the &lt;code&gt;disconnectedCallback&lt;/code&gt; for good hygiene&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Good luck. &lt;a href=&#34;https://indieweb.social/@myobie&#34;&gt;Let me know&lt;/a&gt; if I made any mistakes or missed any other scenarios that are tricky with custom elements.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>How to manage reactive state using Custom Elements (Web Components)</title>
      <link>https://nathanherald.com/posts/reactive-state-in-custom-elements/</link>
      <pubDate>Sun, 17 Nov 2024 11:39:10 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/reactive-state-in-custom-elements/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TL;DR: signals work well in custom elements and grabbing state from a parent through event dispatch is great. &lt;a href=&#34;https://codepen.io/myobie/pen/JjgbxyN&#34;&gt;Checkout this codepen.&lt;/a&gt; 💪&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;To make things truly &lt;em&gt;reactive&lt;/em&gt; in our custom elements (web components) we need some reactive state primitive. In frameworks, we usually use a hook or some other kind of “handle” to some state &lt;em&gt;somewhere out there&lt;/em&gt; in a place we can’t really see.&lt;/p&gt;
&lt;p&gt;This is how I would do this in preact:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-jsx&#34; data-lang=&#34;jsx&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;App&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;updateCount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;useState&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;increment&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;updateCount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;You&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;’&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ve&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;clicked&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;the&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;button&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;s&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;onClick&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;increment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&amp;gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Increment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Where is &lt;code&gt;count&lt;/code&gt; really? We don’t know. It’s &lt;em&gt;in preact.&lt;/em&gt; Which is fine most of the time.&lt;/p&gt;
&lt;p&gt;But still, isn’t that kinda weird. This “function component” &lt;code&gt;App&lt;/code&gt; doesn’t take any arguments. And yet, as I call the function over and over, it returns different results depending on some state somewhere not in the function…&lt;/p&gt;
&lt;p&gt;Also, we can’t actually call &lt;code&gt;App()&lt;/code&gt; ourselves, it will throw… so it’s a function, but not a function for us to use. It’s a function for preact.&lt;/p&gt;
&lt;p&gt;This puts a lot of pressure on the UI framework – it now must contain the definition of the views, probably some styling, the state, the behavior, &lt;strong&gt;all of it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Writing a unit test for the “counting feature” is also not that easy, we need to setup a render loop, etc. We don’t know where that state is, so we gotta let the framework help us.&lt;/p&gt;
&lt;p&gt;It’d be better if our state &lt;em&gt;itself&lt;/em&gt; was reactive and our UI just showed that reactive state…&lt;/p&gt;
&lt;h3 id=&#34;signals&#34;&gt;Signals&lt;/h3&gt;
&lt;p&gt;Every framework is re-discovering signals these days, and that’s a great thing. &lt;em&gt;(Checkout the old &lt;a href=&#34;https://github.com/adamhaile/S&#34;&gt;S&lt;/a&gt; library, which is still there after all these years and still works fine… signals have been great for a while.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Signals can make your state reactive, let you model your app purely as data, and you can unit test your app’s state without having to write any JSX or do any rendering or any of that.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;increment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then our app can just focus on the DOM we wish we had:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-jsx&#34; data-lang=&#34;jsx&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;increment&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;./count.js&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;App&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;You&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;’&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ve&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;clicked&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;the&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;button&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;s&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;onClick&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;increment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&amp;gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Increment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And if somewhere else needed to either display or increment the count, they could import the functionality the same way. Anytime the count changes, any UI component using its value will re-render.&lt;/p&gt;
&lt;p&gt;This takes a lot of pressure off the UI: it can focus only on the actual UI stuff.&lt;/p&gt;
&lt;p&gt;You can imagine writing some tests like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;it&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;counts&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;assertEquals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;increment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;assertEquals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;signals-work-great-in-custom-elements-too&#34;&gt;Signals work great in custom elements too&lt;/h3&gt;
&lt;p&gt;We can wire that same state straight up to where it’s used in our custom element:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;increment&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;./count.js&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;times&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;computed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;time&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;times&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;CounterApp&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;extends&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;HTMLElement&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;counter-app&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;customElements&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;constructor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;super&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;display&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;contents&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;disposes&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;handleEvent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;type&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;click&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;target&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tagName&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;BUTTON&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;increment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;connectedCallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addEventListener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;click&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;countEl&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;[data-count]&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;timesEl&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;[data-times]&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;disposes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;subscribe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;countEl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;innerText&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;disposes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;subscribe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;timesEl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;innerText&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;button&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;button&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;disabled&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;disconnectedCallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;removeEventListener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;click&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;disposes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;forEach&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cb&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;cb&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;&amp;lt;!-- assuming CounterApp.define() has been run --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;counter-app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;You’ve clicked the button &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;data-count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;0&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;data-times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;times&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;disabled&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;Increment&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;counter-app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;My custom element here requires that you provide the correct HTML: a &lt;code&gt;p&lt;/code&gt; and a &lt;code&gt;button&lt;/code&gt;. It’s fine if a custom element has opinions/requirements about it’s children.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Notice in the initial serialized HTML I’ve disabled the button. Then once the custom element is fully attached, I will enable it. This way we never have an enabled button that can’t work on the page. &lt;strong&gt;Old-school progressive enhancement.&lt;/strong&gt; 🆒&lt;/p&gt;
&lt;p&gt;This component gets the reactive state, encapsulates and wires up the behavior, and then everything is hunky-dory.&lt;/p&gt;
&lt;p&gt;Now, of course, web apps are much larger than this… an entire app’s state &lt;strong&gt;&lt;em&gt;can&lt;/em&gt;&lt;/strong&gt; be modeled, tested, and then consumed by the UI. It is possible. Be optimistic 🤓&lt;/p&gt;
&lt;p&gt;And (I say) it’s preferable to have the state outside the UI rather than keeping the app’s state spread throughout our UI code just because &lt;code&gt;useState()&lt;/code&gt; is the only reactive primitive we have there in our framework…&lt;/p&gt;
&lt;p&gt;Moving the app’s state out of the UI layer is a way we can start to bridge the gap between custom elements and framework components. It also makes the decision of “which framework?” less important, so you can feel more free to experiment.&lt;/p&gt;
&lt;h3 id=&#34;give-it-some-context&#34;&gt;Give it some context&lt;/h3&gt;
&lt;p&gt;Keeping an app’s state in globals can be problematic: it makes testing hard and it makes it impossible to have two counters on the page at once (which are related problems).&lt;/p&gt;
&lt;p&gt;Most frameworks provide a way to setup a context which can have a sort of “scoped state” that is for any children of the context.&lt;/p&gt;
&lt;p&gt;An example in preact might look like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-jsx&#34; data-lang=&#34;jsx&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;CounterContext&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;initial&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;CounterApp&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;CounterContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;CounterContext&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;initial&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;CounterApp&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;CounterContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then we’d have two completely independent counter apps on the page at the same time without having to move our reactive state back into the UI components themselves.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How can we communicate a sophisticated scoped state object (or set of objects) to some of the DOM when using custom elements?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It’s not about “passing data down,” it’s really about &lt;strong&gt;events bubbling up.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&#34;events-are-dispatched-synchronously&#34;&gt;Events are dispatched synchronously&lt;/h4&gt;
&lt;p&gt;Yep. Prepare to feel weird about this. Event dispatch is often incredibly misunderstood. &lt;em&gt;React implemented their own synthetic events because of how weird DOM events are…&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;DOM events are like a lightening strike: an event travels down, then bubbles up. There is a capture phase, then a bubble phase. And that entire traversal is synchronous. 🤔 ⚡&lt;/p&gt;
&lt;p&gt;So we can use the &lt;strong&gt;events go up&lt;/strong&gt; principle of the DOM to grab context from a parent, if there is one.&lt;/p&gt;
&lt;p&gt;First, let’s package up all our app’s state into a “factory” or “generate” function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;computed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://esm.sh/@preact/signals-core&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;genState&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;times&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;computed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;time&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;times&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;increment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can make as many &lt;em&gt;app states&lt;/em&gt; as we like.&lt;/p&gt;
&lt;p&gt;Let’s make a custom element to create, contain, and scope that state:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;genState&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;./state.js&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;CounterContext&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;extends&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;HTMLElement&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;customElements&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;counter-context&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;constructor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;super&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;display&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;contents&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;genState&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;handleEvent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// I just made this event name up
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;type&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;getContext&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;c1&#34;&gt;// if any event comes through wanting a context, I&amp;#39;ll throw mine into the detail of the event here and then stop propigation so no further parents can overwrite which allows contexts to be nested, closest one wins
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;      &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stopPropagation&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;connectedCallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addEventListener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;getContext&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;disconnectedCallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;removeEventListener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;getContext&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;GetContextEvent&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;extends&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Event&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;constructor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;super&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;getContext&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;bubbles&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And now the component which wants to subscribe to some state can check for any context from any parents first, falling back to creating it’s own state if no parents have a context for it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;GetContextEvent&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;./context.js&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;CounterApp&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;extends&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;HTMLElement&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;counter-app&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;customElements&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;constructor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;super&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;display&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;contents&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;disposes&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;handleEvent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;type&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;click&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;target&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tagName&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;BUTTON&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;increment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;connectedCallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;getContext&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;GetContextEvent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// ⚡
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dispatchEvent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;getContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// the .context value has been mutated synchronously by the event listener in the parent node
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;getContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;c1&#34;&gt;// if no parent provides the context, then we will make our own
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;      &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;warn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;no parent context, making local state&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;genState&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addEventListener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;click&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;countEl&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;[data-count]&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;timesEl&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;[data-times]&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;disposes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;subscribe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;countEl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;innerText&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;disposes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;subscribe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;timesEl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;innerText&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;button&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;button&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;disabled&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;disconnectedCallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;removeEventListener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;click&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;disposes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;forEach&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cb&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;cb&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And now we can run multiple of these count apps, some of which can have a shared context, some of which can have an isolated context:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;counter-context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;counter-app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;You’ve clicked the button &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;data-count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;0&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;data-times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;times&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;disabled&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;Increment&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;counter-app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;counter-context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;hr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;&amp;lt;!-- both counter-app&amp;#39;s will share the context state provided by this counter-context --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;counter-context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;counter-app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;You’ve clicked the button &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;data-count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;0&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;data-times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;times&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;disabled&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;Increment&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;counter-app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;counter-app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;Same count here: &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;data-count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;0&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;data-times&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;times&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;disabled&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;Increment&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;counter-app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;counter-context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Now that feels a lot like a “reusable component” to me.&lt;/strong&gt; 💪&lt;/p&gt;
&lt;p&gt;➡️ Checkout this codepen: &lt;a href=&#34;https://codepen.io/myobie/pen/JjgbxyN&#34;&gt;https://codepen.io/myobie/pen/JjgbxyN&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Go ahead and move elements in and out of the contexts in the example… hopefully you are starting to imagine how one can build up a sophisticated app with these loosely coupled components through passing simple string data down, and using events to grab more sophisticated objects / states when necessary.&lt;/p&gt;
&lt;h3 id=&#34;this-will-probably-be-a-standard&#34;&gt;This will probably be a standard&lt;/h3&gt;
&lt;p&gt;There is a Community Proposal for a &lt;a href=&#34;https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md&#34;&gt;Context Protocol&lt;/a&gt; which works exactly the same using event dispatch. Check it out. The event which is dispatched is a little different, it has a &lt;code&gt;callback&lt;/code&gt; function which is called by a parent – so not direct object mutation like my example above.&lt;/p&gt;
&lt;p&gt;So, maybe one day this will be 100% built-in 💪&lt;/p&gt;
&lt;p&gt;Try out the &lt;a href=&#34;https://codepen.io/myobie/pen/JjgbxyN&#34;&gt;codepen&lt;/a&gt;, try this out in your project, and &lt;a href=&#34;https://indieweb.social/@myobie&#34;&gt;let me know how it goes&lt;/a&gt;.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Why passkeys are good and you should be advocating for their use</title>
      <link>https://nathanherald.com/posts/passkeys/</link>
      <pubDate>Mon, 04 Nov 2024 15:41:58 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/passkeys/</guid>
      <description>&lt;ol&gt;
&lt;li&gt;Passkeys are a great “remember me on this device” that always works, doesn’t rely on cookies&lt;/li&gt;
&lt;li&gt;If you are in an ecosystem, they will sync to all your devices&lt;/li&gt;
&lt;li&gt;In the near future, passkeys will even sync across ecosystems&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;TL;DR: We all know physical security keys are incredibly secure. Passkeys are modeled on physical security keys and are both incredibly secure and incredibly easy to use to sign in after they are setup. You get issued one just like a physical security key: **you prove who you are, then you get a secure key that re-proves and re-authorizes you with just a fingerprint or face scan ** or whatever. Finally the “remember me on this device” checkbox will actually work.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Passkeys are the ultimate “remember me on this device.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;How many times have you checked that box and it didn’t work? You cookies are lost or something, who knows. Passkeys don’t rely on cookies, are incredibly secure, so finally “remember me” will actually work.&lt;/p&gt;
&lt;h3 id=&#34;why-tho&#34;&gt;Why tho?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Making people provide a password over and over is incredibly insecure!&lt;/strong&gt; We know this, &lt;strong&gt;we have the data,&lt;/strong&gt; people will make mistakes. Yes, you. You will, statistically, eventually make a mistake with a password.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And passkeys making re-signing in easier + prevent us from doing anything by accident.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You should not be entering in your password over and over, the “remember me” should actually work. It’s way more secure for remember me to work than to ask someone to constantly provide a password – no possible attempts to phish that password.&lt;/p&gt;
&lt;h3 id=&#34;what-passkeys-are-not&#34;&gt;What passkeys are not&lt;/h3&gt;
&lt;p&gt;You get a passkey &lt;strong&gt;after&lt;/strong&gt; you prove who you are. You still need a way to initially prove who you are.&lt;/p&gt;
&lt;p&gt;Passkeys are modeled on physical yubikey-like devices. So imagine getting a passkey in the mail or being handed one in person.&lt;/p&gt;
&lt;p&gt;In the past, I would prove who I am to a company and they would issue me a physical yubikey. They’d write down the serial number of that yubikey and note that I have it. Any device with that yubikey plugged in can be assumed that it is definitely me, I don’t have to provide a password, email, or anything. The key both reminds the service who I am and proves that I am definitely who I was when I was given the key.&lt;/p&gt;
&lt;p&gt;And, just like a physical yubikey, if I were to lose it, then I’d have to re-prove who I was to get a new one.&lt;/p&gt;
&lt;h3 id=&#34;passkeys-are-the-same-as-physical-keys&#34;&gt;Passkeys are the same as physical keys&lt;/h3&gt;
&lt;p&gt;You initially do the more difficult proving step, usually clicking a link in an email or something like that. And that step hopefully has a two factor component, to make sure you really are who you say you are. &lt;strong&gt;You want this step to be difficult,&lt;/strong&gt; because you shouldn’t be doing it very often.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt; you have proven who you are, then you can get a very secure key that will on it’s own &lt;strong&gt;re-prove&lt;/strong&gt; and &lt;strong&gt;re-identify&lt;/strong&gt; you over and over again with just a finger print, face scan, or something similar.&lt;/p&gt;
&lt;p&gt;Imagine it. &lt;em&gt;Finally the “remember me on this device” checkbox will actually work.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;are-passkeys-ready-today&#34;&gt;Are Passkeys ready today?&lt;/h3&gt;
&lt;p&gt;Passkeys are good today, because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Passwords are 100% known to be problematic,&lt;/strong&gt; we have the data. You may think you are not going to fall for a phishing attempt, but anyone can and does. Don’t be so arrogant to imagine that you can’t. And phishing isn’t the only problem, it’s a huge deal and no one is immune.&lt;/li&gt;
&lt;li&gt;Even if you had to do the more difficult proving step for each of your devices, it’s only one time per device. After that, use biometrics or similar and you are in. No cookies, no password to remember or lose, you are good now forever on this device. This &lt;strong&gt;is unquestionably easier&lt;/strong&gt; for normal and power users. &lt;strong&gt;“Remember me on this device” finally works.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Good ecosystems already sync passkeys: apple, google, 1password, etc. If you use them, then you only have to sign in the more difficult way one time, period. &lt;strong&gt;Then you are good on all your devices in that ecosystem.&lt;/strong&gt; If you use two ecosystems, then you’ll have to do the difficult sign in two times (one per ecosystem), which is fine. (And, they are working on cross-ecosystem passkey sharing, so you only have to do the difficult prove who you are step once across all types of devices!)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;We all know physical security keys are incredibly secure.&lt;/em&gt; &lt;strong&gt;Passkeys are modeled on physical security keys&lt;/strong&gt; and are both &lt;strong&gt;incredibly secure&lt;/strong&gt; and &lt;strong&gt;incredibly easy&lt;/strong&gt; to use after they are setup.&lt;/p&gt;
&lt;p&gt;You get issued one just like a physical security key: you prove who you are, then you get a secure key that re-proves and re-authorizes you with just a fingerprint or face scan or whatever. Any device with that key can be 100% trusted.&lt;/p&gt;
&lt;p&gt;Sure, you still have to prove who you are one time in a more difficult way. It’s one time. And I would suggest that it be made even more difficult to prove who you are, since you won’t have to do it very often anymore. It should be pretty difficult to get a new passkey.&lt;/p&gt;
&lt;p&gt;And you don’t have to worry about losing a passkey: you can just do the more difficult “prove who you are” step again and make a new one, just like requesting a new hardware key. There will always continue to be “account recovery” steps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passkeys really are just an incredibly secure and easy “remember me on this device” that works.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;some-faqs&#34;&gt;Some FAQs&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Are passkeys useful as a second factor:&lt;/em&gt; I would say no. They are meant to be used as the ultimate “remember me on this device” and that isn’t what we think of as a second factor.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;What if someone gains access to my email, can’t they just get a passkey issued to them?&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;If the service requires both an email link click + a second factor (like a one time passcode), then no. The attacker won’t have the one time passcode (the second factor).&lt;/li&gt;
&lt;li&gt;If a service really only checks email and then issues a passkey, then yes that’s bad. That service would let anyone pretend to be you if they have access to your inbox today. So the service is very insecure and it’s unrelated to passkeys.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Can I still have a password?&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;Yes, a password could be part of the “prove who you are in a more difficult way” flow. However, passwords are known to be terrible, people do make mistakes and enter them into the wrong places, and so it is a risk to allow someone to get a passkey after entering a password. The service really should implement a second factor. A password + second factor as the “difficult path to get a passkey” is perfectly fine, and that lets the passkey focus on being the ultimate “remember me on this device.” &lt;strong&gt;So take how you sign in today + actually remember me on this device that works.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

        
      </description>
    </item>
    <item>
      <title>The disharmony between “web components” (custom elements) and react-like “framework components” is real</title>
      <link>https://nathanherald.com/posts/disharmony-between-components/</link>
      <pubDate>Wed, 30 Oct 2024 11:08:47 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/disharmony-between-components/</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;strong&gt;The primary difference is passing down data vs. passing down behavior, and this causes a ton of confusion.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.abeautifulsite.net/posts/web-components-are-not-the-future-they-re-the-present/&#34;&gt;Recent&lt;/a&gt; &lt;a href=&#34;https://x.com/rich_harris/status/1839484645194277111?s=46&amp;amp;t=5XjJH-Qs6-qiNabMDykXPg&#34;&gt;discourse&lt;/a&gt; about “web components” vs react-like “framework components” is continuing to highlight a fundamental disconnect between these two types of components.&lt;/p&gt;
&lt;p&gt;There is one main irreconcilable difference that many seem to forget to highlight:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The DOM is about passing data down and dispatching events up, while framework components are all about passing behavior down through “props.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And this is why it’s sometimes difficult for framework authors to support custom elements – they are fundamentally incompatible models.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://lit.dev&#34;&gt;lit&lt;/a&gt; tries to bridge the gap by making custom elements look like react, &lt;a href=&#34;https://preactjs.com/guide/v10/web-components/&#34;&gt;preact&lt;/a&gt; does a pretty good best-effort to support custom elements, &lt;a href=&#34;https://react.dev/reference/react-dom/components#custom-html-elements&#34;&gt;react&lt;/a&gt; is also trying, but in the end they can’t work well together because of these fundamental differences.&lt;/p&gt;
&lt;h2 id=&#34;data-down-events-up-vs-passing-behavior-down&#34;&gt;Data down, events up vs. passing behavior down&lt;/h2&gt;
&lt;p&gt;The DOM is an “object model” that is very &lt;a href=&#34;https://www.enjoyalgorithms.com/blog/message-passing-oops&#34;&gt;object oriented&lt;/a&gt;. One of the tenets of most object oriented systems is to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Encapsulate behavior and state into objects, and&lt;/li&gt;
&lt;li&gt;Pass messages / data between those objects. The DOM pretty much works this way.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;code&gt;HTMLParagraphElement&lt;/code&gt; inside the &lt;code&gt;HTMLDIVElement&lt;/code&gt; doesn’t share any state with its parent. It doesn’t need to.&lt;/p&gt;
&lt;p&gt;This is why custom elements are classes: classes are one of the things that can encapsulate state and behavior in javascript. &lt;em&gt;(Sure, they could have been plain objects and I do kinda wish they were, but I get why the spec writers made this choice.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let’s say you want to &lt;em&gt;react&lt;/em&gt; to a click of a button inside your custom element – you &lt;strong&gt;shouldn’t pass a function down&lt;/strong&gt; to the button to provide to its &lt;code&gt;onclick&lt;/code&gt; prop, nah you’d just implement &lt;code&gt;handleEvent&lt;/code&gt; in the custom element which contains the button and listen for the event to bubble up. You don’t even need to know if there are any buttons inside your element, you can just be ready for when there are. And the button doesn’t have to know the custom element is a parent – it dispatches a click either way.&lt;/p&gt;
&lt;p&gt;The custom element encapsulates the behavior like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;CaptureClick&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;extends&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;HTMLElement&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;customElements&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;capture-click&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;constructor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;super&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;display&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;contents&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;handleEvent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;type&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;click&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;target&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tagName&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;BUTTON&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;preventDefault&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stopPropagation&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;warn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;captured&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;target&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;innerText&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;connectedCallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addEventListener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;click&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;disconnectedCallback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;removeEventListener&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;click&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And this can be used either in javascript with &lt;code&gt;document.createElement()&lt;/code&gt; or in HTML like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;&amp;lt;!-- assuming CustomClick.define() has been run --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;capture-click&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;Yo!&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;capture-click&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To someone who works mostly in react-like frameworks, this code is probably very surprising.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Most framework components today are “functional” or at least &lt;em&gt;very function oriented&lt;/em&gt;. If you created a class component today someone would ask you what you were doing… and functional components can be really useful if you want to unit test the output from them. You could provide the function some input and then assert on the virtual DOM nodes returned… although I bet you don’t do that. Have you ever unit tested the output of your function components?&lt;/p&gt;
&lt;p&gt;I bet your components need some sort of “context” to work at all, and I bet you need to setup a component hierarchy to provide that context… so not as functional as we hoped, but still pretty nice.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To implement the above in &lt;a href=&#34;https://preactjs.com/guide/v10/web-components/&#34;&gt;preact&lt;/a&gt;, I’d just do:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// assuming there is a Button component
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;App&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;buttonValue&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Yo!&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;onClick&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;preventDefault&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stopPropagation&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;warn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;captured&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;buttonValue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Button&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;onClick&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;onClick&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;buttonValue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I am &lt;strong&gt;passing behavior down,&lt;/strong&gt; from &lt;code&gt;App&lt;/code&gt; to &lt;code&gt;Button&lt;/code&gt;. And you see this all over framework component code.&lt;/p&gt;
&lt;p&gt;And not just clicks. We pass down functions that query databases, we pass down databases themselves, we store things in a context and then pull it back out many levels down, we pass down callbacks or promises or both…&lt;/p&gt;
&lt;p&gt;And hooks aren’t helping – hooks are just hiding the implementation details of state to make things look functional, when they really aren’t.&lt;/p&gt;
&lt;p&gt;And listen, I love writing “function components” and I like building apps with preact. &lt;a href=&#34;https://new.space/&#34;&gt;new.space&lt;/a&gt; is 100% written with preact, signals, and other great libraries. It’s a nice world to be in.&lt;/p&gt;
&lt;p&gt;JSX makes it easy to pass behavior down. HTML isn’t JSX tho: &lt;strong&gt;HTML is a serialization of a DOM we wish existed.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Serialized DOM (HTML) can only pass data as strings in attributes. I’m sure you’ve read something about “attributes vs. props” before and this is main conflict: html attributes are only data and only strings.&lt;/p&gt;
&lt;h2 id=&#34;how-to-bridge-the-gap&#34;&gt;How to bridge the gap?&lt;/h2&gt;
&lt;p&gt;If you are really motivated, then you can write your custom elements in a DOM-first style and write your react-like components in that style… but I don’t recommend it. It can be a pretty big context switch going back and forth. For existing apps, I recommend staying in that app’s current world. For new apps, build with custom elements… until you can’t.&lt;/p&gt;
&lt;p&gt;Custom elements are ready today, you can do anything you want with them. If you want to build a context element to hold some state and then query that state further down the DOM tree, do a quick &lt;code&gt;this.closest(&#39;parent-with-state&#39;)?.state&lt;/code&gt; inside the &lt;code&gt;connectedCallback()&lt;/code&gt; of the child element.&lt;/p&gt;
&lt;p&gt;What problems have you not been able to solve with custom elements? What would you love to know more about? &lt;a href=&#34;https://indieweb.social/@myobie&#34;&gt;Let me know&lt;/a&gt; and I’ll write up some real examples.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Reminded of the past, and to not repeat it</title>
      <link>https://nathanherald.com/posts/reminded-of-the-past/</link>
      <pubDate>Sun, 27 Oct 2024 20:35:57 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/reminded-of-the-past/</guid>
      <description>&lt;p&gt;In Berlin I am confronted with reminders of the city’s every single day. Every time I walk outside I see a memorial or reminder of dreadful things, it’s normal, these reminders are ever present.&lt;/p&gt;
&lt;p&gt;Earlier this year I was walking around, admiring some wonderful murals…&lt;/p&gt;
&lt;div class=&#34;row-of-photos&#34; data-cols=&#34;&#34;&gt;
  

&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the GitHub Actions bot timeline entry for a deployment&#34;
    width=&#34;795&#34;
    height=&#34;1200&#34;
    src=&#34;https://nathanherald.com/posts/reminded-of-the-past/1@2x_hu18341951740692150590.jpg&#34;&gt;
&lt;/figure&gt;


&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the GitHub Actions bot timeline entry for a deployment&#34;
    width=&#34;1200&#34;
    height=&#34;795&#34;
    src=&#34;https://nathanherald.com/posts/reminded-of-the-past/2@2x_hu12586199087603843797.jpg&#34;&gt;
&lt;/figure&gt;


&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the GitHub Actions bot timeline entry for a deployment&#34;
    width=&#34;795&#34;
    height=&#34;1200&#34;
    src=&#34;https://nathanherald.com/posts/reminded-of-the-past/3@2x_hu3986155756693626964.jpg&#34;&gt;
&lt;/figure&gt;



&lt;/div&gt;

&lt;p&gt;…when I learned about something I didn’t know before. One of these buildings had an informational sign about it. I took a photo of the sign, but this copy on Wikimedia is more clear than my photo:&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://upload.wikimedia.org/wikipedia/commons/d/d3/Gedenktafel_Friedrichstr_234_%28Kreuz%29_Gutschow-Keller.jpg&#34;&gt;
  &lt;img
    alt=&#34;Historical sign about the Gutschow-Keller&#34;
    width=&#34;727&#34;
    height=&#34;1200&#34;
    src=&#34;https://nathanherald.com/posts/reminded-of-the-past/sign@2x_hu5985641581240537443.jpg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    The sign reads: ‘The early concentration camp “Gutschow-Keller” was located here.’
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;The greengrocers Hermann and Paul Gutschow owned the imposing building in Wilhelminian style in Friedrichstraße 17 on the opposite side of the street. Already in 1932, they placed their warehouse and basement at the disposal of the &amp;ldquo;SA-Sturmbann Ill/8&amp;rdquo;. These rooms were located here in the second courtyard of the building in Friedrichstraße 234.&lt;/p&gt;
&lt;p&gt;From. March to May 1933, the place was one of the first concentration camps in Berlin. Prisoners called it &amp;ldquo;Blutburg&amp;rdquo; (castle of blood). Hundreds of trade unionists, communists, social democrats and Jews were seized in their homes, at their places of work and in the middle of the street and were then abducted to this place. Interrogations, torture and humiliation followed - often for days on end.&lt;/p&gt;
&lt;p&gt;As these torture chambers of the SA were placed in a large tenement block, the neighbourhood knew about the imprisonment and maltreatment, for the screams of the prisoners could still be heard on the street.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;People were rounded up, detained, and tortured. The neighbors here saw them, could hear their screams, and they did nothing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This reminder is here in this neighborhood because this really happened, and it could happen again.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And it wasn’t a big, flashy production by “evil people” the way most people probably imagine the history was here in Berlin. These were normal people, it was boring, it was methodical, they were instructed to do it, and it relied on everyone to just follow orders and/or just let it happen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;These reminders are everywhere here in Berlin because these things really happened, and they could happen again.&lt;/strong&gt;&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>How I (Now) Backup My Photos</title>
      <link>https://nathanherald.com/posts/backup-your-photos/</link>
      <pubDate>Tue, 14 May 2024 23:13:30 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/backup-your-photos/</guid>
      <description>&lt;p&gt;I’m taking more and more RAW digital photos + scans of film photos and I finally took the time to regularly back them up – &lt;strong&gt;because I was locked out of my Apple account!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Apple had a problem back on April 26 where some people’s accounts got locked, and it affected me. I woke up and saw that my Apple account was locked, and my phone demanded that I reset my password. So I reset it. Then, after a couple minutes, it demanded that I reset my password yet again.&lt;/p&gt;
&lt;p&gt;I’ve seen how these things can go, and getting into a reset loop like that felt like it could end up locking me out of my Apple account entirely. Before: Apple Photos was my only storage + backup of all 800GB of my photos. Now: I need to run to the electronics store and buy a hard drive.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;You can read more about Apple’s issue on &lt;a href=&#34;https://mjtsai.com/blog/2024/04/26/janky-apple-id-security/&#34;&gt;Michael Tsai’s blog post which has tons of links&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I turned my iPhone and Mac off, I didn’t want them to accidentally do anything while I was out. I had recently got a &lt;a href=&#34;https://de.nothing.tech/pages/phone-2a&#34;&gt;Nothing 2a&lt;/a&gt; to test my web work on Android, so I grabbed it and put a prepaid SIM into it so I could do stuff out and about with a phone that I’m not locked out of.&lt;/p&gt;
&lt;p&gt;Then I went down to &lt;a href=&#34;https://www.saturn.de&#34;&gt;Saturn&lt;/a&gt; and picked up a &lt;a href=&#34;https://semiconductor.samsung.com/consumer-storage/portable-ssd/t7-shield/&#34;&gt;Samsung T7 Shield&lt;/a&gt; drive. Luckily, I had already done a lot of research about which hard drives are reliable, so I knew these were good. There are cheaper drives that have terrible track records (Sandisk, WD, …) and I didn’t want to deal with that on a stressful day where I felt I might lose access to all my photos forever. I grabbed a 4TB drive so I knew I’d have plenty  of space for a while.&lt;/p&gt;
&lt;p&gt;I’ve also done a lot of research about which backup program to use on my Mac, and so I knew the best is &lt;strong&gt;&lt;a href=&#34;https://www.arqbackup.com&#34;&gt;Arq&lt;/a&gt;&lt;/strong&gt;. I had already learned that if the drive isn’t plugged in, Arq will just silently fail. I use a MacBook Pro so the T7 might not be plugged in every time Arq runs, and Arq can just handle that scenario quietly without bothering me.&lt;/p&gt;
&lt;p&gt;I’ve already checked the box to keep all my originals on my disk and I’ve got a 2TB disk in the laptop, so all my Apple Photos were already on my Mac’s disk when my Apple account got locked. As long as Apple’s iCloud stuff didn’t decide to remove them from my disk because my account was locked and it thought it might be the secure thing to do, then I’d be alright.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the Settings window of Photos on macOS&#34;
    width=&#34;712&#34;
    height=&#34;492&#34;
    src=&#34;https://nathanherald.com/posts/backup-your-photos/photos-settings@2x_hu15561928455133977291.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;I’ve also been using Adobe Photoshop Lightroom (the cloud-ish one) some for the past few months, so I chose to backup those onto my local disk as well.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the Settings window of Adobe Photoshop Lightroom&#34;
    width=&#34;737&#34;
    height=&#34;644&#34;
    src=&#34;https://nathanherald.com/posts/backup-your-photos/adobe-settings@2x_hu6065329436301553644.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;So I got back, booted up the Mac, installed Arq, and chose to backup my Photos and Lightroom libraries to the T7 💪 It took a few hours… then I felt a lot better.&lt;/p&gt;
&lt;p&gt;I went ahead and reset my password again. By this time Apple seemed to have gotten a handle on whatever was going on, and after this second password reset things stabilized.&lt;/p&gt;
&lt;p&gt;Still, I won’t ever trust iCloud Photos to be the only place where my photos live – I will make sure they are always somewhere else.&lt;/p&gt;
&lt;p&gt;And to that end, now I had a physical backup here, but it is best practice to also have an off-site backup… so I signed up for &lt;a href=&#34;https://wasabi.com&#34;&gt;Wasabi&lt;/a&gt;. They are one of the cheapest, yet still reliable, places to store stuff online and they only charge for storage – all the bandwidth / transfer is free.&lt;/p&gt;
&lt;p&gt;I knew this would take a while, my upload speed at home isn’t great, and Arq handled it really well. It took more than 2 days for everything to initially upload, and now Arq is smart and uploads anything new every night.&lt;/p&gt;
&lt;p&gt;So, because of Apple locking my account and scaring me, I now have nightly backups of my photos to a physical drive and a cloud drive:&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of my two Arq backups, one to the T7 and one to Wasabi&#34;
    width=&#34;277&#34;
    height=&#34;283&#34;
    src=&#34;https://nathanherald.com/posts/backup-your-photos/arq-menu@2x_hu16872458985134515199.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;It is tempting to feel grateful that Apple had this dumb account locking issue, because it spurred me to finally setup backups for my photos… but no it was still dumb. It really proved to me: &lt;strong&gt;my iPhone and my Apple account are too important.&lt;/strong&gt; If I lose access, then I lose quite a lot… which is pretty annoying.&lt;/p&gt;
&lt;p&gt;Smart Phones were fun, little, powerful toys for a while. Now they are required for day-to-day life. &lt;strong&gt;I am now motivated to start to &lt;em&gt;unbundle&lt;/em&gt; stuff from my phone.&lt;/strong&gt;&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>How I restored and setup my new (to me) $10 iPod shuffle (2nd Gen) with my M1 Mac running Sonoma</title>
      <link>https://nathanherald.com/posts/ipod-shuffle/</link>
      <pubDate>Tue, 30 Apr 2024 00:21:41 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/ipod-shuffle/</guid>
      <description>&lt;p&gt;I’ve had a hankering for an iPod for a while, so I decided to take the plunge and get a shuffle to try it out. My phone is too important now! And I don’t really want to drain the battery all the time when I’m out and about… I already have a camera, now a music player, if I buy a physical map then I’ll have my own App Store in my backpack 🤦&lt;/p&gt;
&lt;p&gt;Getting the iPod shuffle setup wasn’t straightforward… so here is how I got it all working.&lt;/p&gt;
&lt;p&gt;I bought mine on &lt;a href=&#34;https://www.kleinanzeigen.de&#34;&gt;Kleinanzeigen&lt;/a&gt; for 10 bucks. The person was very prompt. They let me use the in-app purchase system – which I prefer and recommend, don’t “send PayPal friends and family” to people. Not too scary, it all Just Worked™&lt;/p&gt;
&lt;h2 id=&#34;1-restore&#34;&gt;1) Restore…&lt;/h2&gt;
&lt;p&gt;Restoring on macOS isn’t as easy anymore. The parts of iTunes that used to sync with iPods is now in the Finder! And my Finder crashed when I tried to restore the iPod. When I tried again, it hung forever and never finished. &lt;em&gt;(If your Finder allows you to restore the iPod then you can skip right to step 2!)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What I learned is: &lt;strong&gt;iTunes still works and you can still download iTunes on Windows&lt;/strong&gt; 💪&lt;/p&gt;
&lt;p&gt;Running Windows is actually pretty easy these days with &lt;a href=&#34;https://mac.getutm.app&#34;&gt;UTM&lt;/a&gt;. I had remembered companies like Parallels and VM Ware, but those cannot emulate x86 Windows on my M1 (ARM) Mac and we need the x86 version of iTunes… so UTM is it.&lt;/p&gt;
&lt;h3 id=&#34;utm-is-a-whole-deal&#34;&gt;UTM is a whole deal&lt;/h3&gt;
&lt;p&gt;While UTM is free and it works well, it is not easy to figure out how to get it all working correctly.&lt;/p&gt;
&lt;p&gt;And sadly, the best info I found is all glommed up in this YouTube video: &lt;a href=&#34;https://www.youtube.com/watch?v=KsNoGPczavw&#34;&gt;How to install Windows 10 on M1 Mac – Full Tutorial using UTM app&lt;/a&gt;. Now &lt;em&gt;I am very grateful that this video exists,&lt;/em&gt; but I prefer to not need to pause, back up, go forward, etc for an hour…&lt;/p&gt;
&lt;p&gt;Here are the important steps in text:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install &lt;a href=&#34;https://mac.getutm.app&#34;&gt;UTM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Download a Windows 10 ISO from here: &lt;a href=&#34;https://www.microsoft.com/en-us/software-download/windows10ISO&#34;&gt;https://www.microsoft.com/en-us/software-download/windows10ISO&lt;/a&gt; (an ISO is a virtual CD, so this is basically the install CD)&lt;/li&gt;
&lt;li&gt;Download the drivers you need from here: &lt;a href=&#34;https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.190-1/&#34;&gt;https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.190-1/&lt;/a&gt; (these are the Fedora drivers and they will help UTM know how to get Windows 10 working well, these are also on a virtual CD)
&lt;ul&gt;
&lt;li&gt;Do not get the latest version of these drivers, only use this specific 190 version linked above. It’s tempting to use a version in the 200s, it feels like we should use the newest ones, but don’t do it. Use this 190 version.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Download the “Guest Tools” from here: &lt;a href=&#34;https://github.com/utmapp/qemu/releases/download/v7.0.0-utm/spice-guest-tools-0.164.4.iso&#34;&gt;https://github.com/utmapp/qemu/releases/download/v7.0.0-utm/spice-guest-tools-0.164.4.iso&lt;/a&gt; (you can read more about the “Guest Tools” here &lt;a href=&#34;https://docs.getutm.app/guest-support/windows/&#34;&gt;https://docs.getutm.app/guest-support/windows/&lt;/a&gt;, they just make UTM work better with macOS…)&lt;/li&gt;
&lt;li&gt;Download and open a UTM VM template: &lt;a href=&#34;https://web.archive.org/web/20230218145944/https://chrisp.cafe/UTM/Windows_10_x64.utm.zip&#34;&gt;https://web.archive.org/web/20230218145944/https://chrisp.cafe/UTM/Windows_10_x64.utm.zip&lt;/a&gt; (this is an archived copy of a template which used to be on chrisp.cafe’s website 🤷)
&lt;ul&gt;
&lt;li&gt;Move the template to where you want to keep your virtual Windows 10 machine. Don’t keep it in Downloads or put it in your iCloud Drive or anything like that. Your Windows VM will eventually be multiple GB in size and I ended up with many GBs of stuff in my Downloads folder, which I didn’t want.&lt;/li&gt;
&lt;li&gt;Double click the template file to open UTM and you’ll see it in the UTM sidebar and a play button to start it up. &lt;strong&gt;Click play to run it.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Using this UTM template is important because it’s already setup to prevent you from having weird network issues during the Windows install.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use the CD icon in the toolbar to change to the windows 10 ISO as the disk&lt;/li&gt;
&lt;li&gt;Reboot, and make sure to hit space so it boots from a CD or DVD
&lt;ul&gt;
&lt;li&gt;If you pay attention when the computer is booting up, you’ll see it says “press any key to boot from CD or DVD” and that’s why we hit a key – we want to boot from our Windows 10 ISO&lt;/li&gt;
&lt;li&gt;This will boot into the Windows installer 🙌 we are getting there&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Choose your language and tell it you don’t have a product key
&lt;ul&gt;
&lt;li&gt;You won’t have to pay for Windows. You can use Windows for a short time without entering a product key and we will be done before that time is over. And we can always start over with a fresh VM in UTM if it starts to complain.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Choose Windows 10 Pro. Choose Custom Install (advanced).&lt;/li&gt;
&lt;li&gt;When you get to the “choose a disk” screen, there won’t be any disks visible. We need to install the drivers so Windows can see them.
&lt;ol&gt;
&lt;li&gt;Use the CD icon to change to the virtio ISO file, the drivers virtual disk we downloaded. We are going to load 3 drivers.&lt;/li&gt;
&lt;li&gt;Click “Load driver”&lt;/li&gt;
&lt;li&gt;Navigate into the CD’s directories with “Browse”&lt;/li&gt;
&lt;li&gt;&lt;code&gt;smbus &amp;gt; 2k8 &amp;gt; amd64&lt;/code&gt;, “Next”&lt;/li&gt;
&lt;li&gt;“Load driver” again, “Browse”&lt;/li&gt;
&lt;li&gt;&lt;code&gt;qxldod &amp;gt; w10 &amp;gt; amd64&lt;/code&gt;, “Next”&lt;/li&gt;
&lt;li&gt;“Load driver” one last time, “Browse”&lt;/li&gt;
&lt;li&gt;&lt;code&gt;viostor &amp;gt; w10 &amp;gt; amd64&lt;/code&gt;, “Next”&lt;/li&gt;
&lt;li&gt;Now the Drive shows up and can be chosen to install Windows onto it, but you can’t proceed with “Next” yet…&lt;/li&gt;
&lt;li&gt;Use the CD icon to change back to the Windows 10 ISO&lt;/li&gt;
&lt;li&gt;Click “Refresh”&lt;/li&gt;
&lt;li&gt;Now you can click “Next”, and now it will take a very long time… until it asks you to provide a username&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Provide a username, leave the password blank, “Next”, and it boots into Windows 🎉&lt;/li&gt;
&lt;li&gt;Use the CD icon to change to the Guest Tools ISO
&lt;ol&gt;
&lt;li&gt;Open the CD in the Explorer&lt;/li&gt;
&lt;li&gt;Run the spice guest tools application on the CD&lt;/li&gt;
&lt;li&gt;Eject the CD once it’s done&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Shutdown Windows
&lt;ul&gt;
&lt;li&gt;If the screen is black for a long time, the VM doesn’t know that windows has quit so you can just power it off yourself (it’s like having a real, old computer…)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click the icon to edit the VM’s options&lt;/li&gt;
&lt;li&gt;Add a Network where the Disk stuff is
&lt;ul&gt;
&lt;li&gt;Make sure it’s a &lt;code&gt;virtio-net-pci&lt;/code&gt; one&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Change the Display graphics to &lt;code&gt;virtio-gpu-cpi&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Don’t choose Retina, it will be sloooooow&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Now download and install an old iTunes version that still works
&lt;ul&gt;
&lt;li&gt;Download and install iTunes from this page: &lt;a href=&#34;https://support.apple.com/en-us/106372&#34;&gt;https://support.apple.com/en-us/106372&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Don’t download iTunes from the Microsoft Store it can’t deal with iPods&lt;/li&gt;
&lt;li&gt;You’ll need to restart Windows after installing it&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Plug in your iPod and UTM will try to grab the USB device from macOS, you’ll need to say “OK” or “Allow” twice for it to work. The iPod will now show up in Windows.&lt;/li&gt;
&lt;li&gt;iTunes should now be able to restore it just fine, it might be slow though&lt;/li&gt;
&lt;li&gt;Choose to use it as a disk in iTunes and apply that change&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now you can put music on there from your Mac, but only if UTM is not running, so shutdown Windows and completely quit UTM.&lt;/p&gt;
&lt;h2 id=&#34;2-adding-music&#34;&gt;2) Adding Music…&lt;/h2&gt;
&lt;p&gt;I don’t want to deal with iTunes on Windows or Mac day-to-day… and it turns out one can just use the Finder to put music files on the iPod, run a python script in the Terminal and it will jam all those files into the iPod’s internal database.&lt;/p&gt;
&lt;p&gt;The python script is from: &lt;a href=&#34;https://github.com/andrewfowlie/shuffle-db3&#34;&gt;https://github.com/andrewfowlie/shuffle-db3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First,&lt;/strong&gt; you’ll need to install python and then install one package:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# if you don&amp;#39;t have brew installed, then that is a whole deal and you can read more over at https://brew.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;brew install python
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pip3 install --break-system-packages eyed3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then we can download the python script directly from the code repository.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cd ~/Downloads &amp;amp;&amp;amp; curl -O &amp;#34;https://raw.githubusercontent.com/andrewfowlie/shuffle-db3/master/src/python/rebuild_db3.py&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the Finder, drag the &lt;code&gt;rebuild_db3.py&lt;/code&gt; file from your Downloads folder to the iPod’s drive that is mounted there. It needs to be at the root.&lt;/p&gt;
&lt;p&gt;Now, &lt;strong&gt;second,&lt;/strong&gt; make a folder on the iPod’s drive named “Music” – put your mp3’s and m4a’s in there. You can even do nested folders. The iPod will play things in file order by default, so you can name stuff to get them into the order you want like &lt;code&gt;01&lt;/code&gt;, &lt;code&gt;02&lt;/code&gt;, …&lt;/p&gt;
&lt;p&gt;After you have them all in there, &lt;strong&gt;third,&lt;/strong&gt; you can run this so the iPod will recognize and play those files:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Make sure to replace iPod with the name of your iPod. If you don&amp;#39;t know the name of your iPod, then you can type &amp;#34;cd &amp;#34; and then drag the iPod&amp;#39;s drive from the Finder into the Terminal window and it will insert the path to it.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; /Volumes/iPod/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;python3 rebuild_db3.py Music/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So you’re telling python “rebuild the database using this Music folder.”&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Or, if you’re like me and you will definitely forget all that, you can download my &lt;code&gt;rebuild.command&lt;/code&gt; file and just double click that to run the rebuild script.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd ~/Downloads &amp;amp;&amp;amp; curl -O &amp;quot;https://gist.githubusercontent.com/myobie/5c0c25cc867420ba15885e53a9a3e133/raw/ac91f695f02c5c2b0b1e8a0c5a6398830eeddd08/rebuild.command&amp;quot;
cd ~/Downloads &amp;amp;&amp;amp; chmod +x rebuild.command
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Drag the &lt;code&gt;rebuild.command&lt;/code&gt; file onto the iPod’s drive, and now you can double click that to run the python script for you. You can look here and see that &lt;code&gt;rebuild.command&lt;/code&gt; file is just that one line that starts with &lt;code&gt;python&lt;/code&gt;: &lt;a href=&#34;https://gist.github.com/myobie/5c0c25cc867420ba15885e53a9a3e133&#34;&gt;https://gist.github.com/myobie/5c0c25cc867420ba15885e53a9a3e133&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Tada 🎉&lt;/p&gt;
&lt;p&gt;Now you have working iPod shuffle that can clip on your shirt and provide you music and happiness for 4–8 hours (ᵔᵕᵔ)/&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(If I get time, I’ll try to update this with screenshots… we’ll see…)&lt;/em&gt;&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Content Authenticity Initiative</title>
      <link>https://contentauthenticity.org</link>
      <pubDate>Sat, 27 Jan 2024 12:02:23 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/content-authenticity/</guid>
      <description>&lt;p&gt;I first learned about the Content Authenticity Initiative from &lt;a href=&#34;https://leica-camera.com/en-US/photography/content-credentials&#34;&gt;Leica&lt;/a&gt; with the release of the &lt;a href=&#34;https://leica-camera.com/en-US/photography/content-credentials&#34;&gt;M11-P&lt;/a&gt;, and at first I didn’t completely understand what it was about. Now, I feel sure &lt;strong&gt;this is one of the most important recent developments for photography, journalism, documentaries, and story telling.&lt;/strong&gt; More and more, people are unsure if what they are seeing is “authentic” or “real,” especially when it doesn’t match their presuppositions. People are so quick now to claim “deep fake” or “AI” when something doesn’t make sense. And the only real way to help is: &lt;em&gt;provenance.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The only time I’ve heard &lt;em&gt;provenance&lt;/em&gt; in the past has been related to “can we determine this physical artwork is an original” or something like that. And now, we have the same problem with digital photos and videos: can we determine this is an original work, a documentation of something that really happened? So it makes perfect sense to me that the best answer we have is to develop a better system of digital content provenance – the state goal of the CAI: “Authentic storytelling through digital content provenance.”&lt;/p&gt;
&lt;p&gt;I’m excited to see more companies and organizations join the CAI and to start seeing provenance information show up under images and videos more often 💪&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(And I gotta start saving up now for an M11-P 😅)&lt;/em&gt;&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://contentauthenticity.org&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>1984</title>
      <link>https://glass.photo/csilverman/1fqXQEuxbjYPMdhwaInJbk</link>
      <pubDate>Tue, 23 Jan 2024 10:37:35 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/1984/</guid>
      <description>&lt;p&gt;This morning I came across this image from the great Chris Silverman:&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://glass.photo/csilverman/1fqXQEuxbjYPMdhwaInJbk&#34;&gt;
  &lt;img
    alt=&#34;Painting by Chris Silverman representing the famous Mac commercial: 1984&#34;
    width=&#34;302&#34;
    height=&#34;512&#34;
    src=&#34;https://nathanherald.com/posts/links/1984/photo@2x_hu13189038724230647018.jpeg&#34;&gt;
  &lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;em&gt;You should follow Chris on &lt;a href=&#34;https://glass.photo/csilverman&#34;&gt;Glass&lt;/a&gt; and on &lt;a href=&#34;https://www.threads.net/@csilverman&#34;&gt;Threads&lt;/a&gt; 👈 He uses the iOS Notes app to create amazing paintings. &lt;a href=&#34;https://nathanherald.com/posts/links/notes-art&#34;&gt;I posted a link to him previously&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Wow, the famous Mac commercial “1984” aired 40 years ago! I was two months old 😬 I’ve seen the commercial referenced over and over throughout my life, it’s one of those cultural phenomenons. I use and love my Mac everyday, I’m glad it worked out for them 😅&lt;/p&gt;
&lt;p&gt;If you’ve never seen the commercial, here it is:&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;420&#34; src=&#34;https://www.youtube-nocookie.com/embed/VtvjbmoDx-I&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;40 years is both a long time, and not really that long-a-time…&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://glass.photo/csilverman/1fqXQEuxbjYPMdhwaInJbk&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Don’t Be a Sucker (1947)</title>
      <link>https://www.youtube.com/watch?v=iQ0ct9bglYo</link>
      <pubDate>Mon, 15 Jan 2024 10:29:33 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/dont-be-a-sucker/</guid>
      <description>&lt;p&gt;A short film produced by the United States Department of War released in 1947.&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/iQ0ct9bglYo&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;See also &lt;a href=&#34;https://en.wikipedia.org/wiki/Don%27t_Be_a_Sucker&#34;&gt;the Wikipedia entry for “Don’t Be a Sucker”&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=iQ0ct9bglYo&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>TIL about Promise.withResolvers()</title>
      <link>https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers</link>
      <pubDate>Mon, 18 Dec 2023 11:38:06 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/promise-resolvers/</guid>
      <description>&lt;p&gt;Seen on &lt;a href=&#34;https://m.webtoo.ls/@patak/111484207679580940&#34;&gt;mastodon (@patak@webtoo.ls)&lt;/a&gt;, I am excited to see that this old trick is being standardized:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;resolve&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;reject&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;promise&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Promise&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;rej&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;resolve&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;reject&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;rej&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I’ve been using &lt;a href=&#34;https://deno.land/std@0.189.0/async/deferred.ts?source=&#34;&gt;deno’s standard library’s deferred&lt;/a&gt; for a while, and I’ll be happy to switch to &lt;code&gt;Promise.withResolvers()&lt;/code&gt; soon 💪&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Creating an immediate stereo mute in Eurorack 🤔</title>
      <link>https://nathanherald.com/posts/eurorack-stereo-mute/</link>
      <pubDate>Mon, 13 Nov 2023 13:40:55 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/eurorack-stereo-mute/</guid>
      <description>&lt;p&gt;I’m just starting to learn about &lt;a href=&#34;https://en.wikipedia.org/wiki/Eurorack&#34;&gt;Eurorack&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/Modular_synthesizer&#34;&gt;modular synthesizers&lt;/a&gt;, so I think I’m definitely missing quite a bit here, but I couldn’t find an existing module or an online post for how to push a button to mute two voltages at once…&lt;/p&gt;
&lt;p&gt;So, and I suppose this is a normal feeling when working with modular, &lt;strong&gt;I had to invent my own mute.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’m learning about Eurorack using &lt;a href=&#34;https://vcvrack.com/&#34;&gt;VCV Rack&lt;/a&gt;, the incredible program which emulates voltage controlled modules in a very nice, visual rack. Here is the patch I was working on yesterday (a “patch” just means “I wired things up this way”):&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the entire patch in VCV Rack&#34;
    width=&#34;1200&#34;
    height=&#34;780&#34;
    src=&#34;https://nathanherald.com/posts/eurorack-stereo-mute/patch@2x_hu7116186660945088593.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;I’ve patched &lt;a href=&#34;https://www.modulargrid.net/e/mutable-instruments-plaits&#34;&gt;Plaits&lt;/a&gt; into &lt;a href=&#34;https://www.modulargrid.net/e/mutable-instruments-beads&#34;&gt;Beads&lt;/a&gt;, and I wanted to be able to quickly mute the stereo output of Beads with a single click.&lt;/p&gt;
&lt;p&gt;There is a built-in Mutes module, but it only lets me mute one thing at a time. I want to mute both the left and right output from Beads at exactly the same time with one click. So it’s a no-go.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the Mutes module in VCV Rack&#34;
    width=&#34;333&#34;
    height=&#34;462&#34;
    src=&#34;https://nathanherald.com/posts/eurorack-stereo-mute/mutes@2x_hu8169051763161869598.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;In the short time I’ve been learning about modular I’ve learned that anytime I want to control the volume of something I want to reach for a VCA (voltage controlled amplifier). So I pulled two VCAs out into the rack and just starred for a bit…&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of two VCAs turned down to 0% wired up to a scope module&#34;
    width=&#34;320&#34;
    height=&#34;409&#34;
    src=&#34;https://nathanherald.com/posts/eurorack-stereo-mute/two-vcas@2x_hu2420747990855550486.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;I browsed through all the modules I have in my library in VCV Rack and saw a “Push” module, which is just a push button to go from 0V to 10V when pushed, and then back to 0V when
released. &lt;strong&gt;That is close!&lt;/strong&gt;&lt;/p&gt;

&lt;figure&gt;
&lt;img
  src=&#34;push-button@2x.mov&#34;
  width=&#34;331&#34;
  alt=&#34;Screen recording of pushing the Push button which opens the VCAs while held down&#34;&gt;
&lt;/figure&gt;


&lt;p&gt;What I want is a toggle tho, I don’t want to have to hold the button down. I want to toggle between 0V and 10V.&lt;/p&gt;
&lt;p&gt;Well, after just clicking around for quite a while, I accidentally learned that the “Hold” button in the Push module will “push and hold” the button, so it’s effectively a toggle for on vs. off. 🙌 So I already had all the modules I needed in place.&lt;/p&gt;

&lt;figure&gt;
&lt;img
  src=&#34;hold-button@2x.mov&#34;
  width=&#34;331&#34;
  alt=&#34;Screen recording of pushing the Hold button which toggles the VCAs on and off&#34;&gt;
&lt;/figure&gt;


&lt;p&gt;It was super fun to have to look at the tools available and reason out how to accomplish my goal. I may have chosen a long or difficult path, but I ended up where I wanted to be 💪&lt;/p&gt;
&lt;p&gt;I bet this is why a lot of people get into and enjoy modular and Eurorack… solving problems and puzzles which result in sounds and music is super addictive.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Jordan Ward: Tiny Desk Concert</title>
      <link>https://www.youtube.com/watch?v=RcINbc0ye-o</link>
      <pubDate>Thu, 09 Nov 2023 18:36:46 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/jordan-ward-tiny-desk/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/RcINbc0ye-o&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=RcINbc0ye-o&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Cover of Backwater Blues by Sabrina, originally by Lightnin’ Hopkins</title>
      <link>https://www.youtube.com/watch?v=b1YZO1yrCAs</link>
      <pubDate>Sun, 05 Nov 2023 17:46:04 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/backwater-blues/</guid>
      <description>&lt;p&gt;If you are not familiar with &lt;a href=&#34;https://en.wikipedia.org/wiki/Lightnin%27_Hopkins&#34;&gt;Lightnin’ Hopkins&lt;/a&gt;, then checkout &lt;a href=&#34;https://en.wikipedia.org/wiki/Lightnin%27_Hopkins&#34;&gt;the Wikipedia page about him&lt;/a&gt; and just generally seek out his music 🔊&lt;/p&gt;
&lt;p&gt;Sabrina does a great cover here:&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/b1YZO1yrCAs&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;Here is a recording of Lightnin’ Hopkins:&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/lf_ns1BbTOE&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=b1YZO1yrCAs&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>TIL about “Weltschmerz”</title>
      <link>https://www.psychologytoday.com/au/blog/soul-console/202301/if-youre-feeling-world-wearied-you-may-have-weltschmerz</link>
      <pubDate>Fri, 03 Nov 2023 11:03:40 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/weltschmerz/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The disillusionment we feel when the world falls short of our expectations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Found &lt;a href=&#34;https://www.densediscovery.com&#34;&gt;via the Dense Discovery newsletter&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.psychologytoday.com/au/blog/soul-console/202301/if-youre-feeling-world-wearied-you-may-have-weltschmerz&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>That photograph on the side of the house on Gartenstraße in Berlin…</title>
      <link>https://nathanherald.com/posts/gartenstra%C3%9Fe/</link>
      <pubDate>Mon, 30 Oct 2023 11:14:27 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/gartenstra%C3%9Fe/</guid>
      <description>&lt;p&gt;This past weekend I saw a person with a nice Leica rangefinder camera standing outside the shop my partner and I were in, so I asked him about his camera, how long he had been a photographer, etc. I am getting more and more into photography and I love hearing from others about their photography, cameras, etc.&lt;/p&gt;
&lt;p&gt;It turned out I met Christian Klopf, a doctor here in Berlin. He said he moved to Berlin in 1986 and has been taking photos here for decades.&lt;/p&gt;
&lt;p&gt;Then, on his phone, he showed me an old photo he had taken and I recognized it immediately – his photo is printed on this house:&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Photograph of the house on Gartenstraße which has a photograph from 1990 printed on it&#34;
    width=&#34;795&#34;
    height=&#34;1200&#34;
    src=&#34;https://nathanherald.com/posts/gartenstra%C3%9Fe/house@2x_hu2797975480609675115.jpg&#34;&gt;
  &lt;figcaption&gt;
    Christian Klopf’s photo of Gartenstraße from 1990
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I walk past this photo often. There are many photos setup along the historical path of the wall in my neighborhood and it never occurred to me that &lt;em&gt;the people who took those photos might still live here,&lt;/em&gt; might still be taking photos nearby. I mean &lt;em&gt;of course,&lt;/em&gt; but also &lt;em&gt;wow.&lt;/em&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Photograph of the sign with attribution of Christian Klopf’s photograph&#34;
    width=&#34;1200&#34;
    height=&#34;794&#34;
    src=&#34;https://nathanherald.com/posts/gartenstra%C3%9Fe/sign@2x_hu18156179785350383096.jpg&#34;&gt;
  &lt;figcaption&gt;
    “Border strip and rear border area between Gartenstrasse
and the Sophien parish cemetery, 1990”&lt;br&gt;
– photo by Christian Klopf
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Sometimes taking photos of &lt;em&gt;mundane subjects&lt;/em&gt; or everyday activities can seem &lt;em&gt;unimportant.&lt;/em&gt; However, it’s good to be reminded that documenting and capturing the state of the world is always valuable. You never know when a photo might end up being historically important or even a bit famous (famous to me at least 😆).&lt;/p&gt;
&lt;p&gt;It was great to meet &lt;a href=&#34;https://www.instagram.com/christianklopfberlin/&#34;&gt;Christian and you can follow him on Instagram&lt;/a&gt;, like I do.&lt;/p&gt;
&lt;p&gt;I am very inspired to get out there and take more photos. 💪&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Unbundling AI by Benedict Evans</title>
      <link>https://www.ben-evans.com/benedictevans/2023/10/5/unbundling-ai</link>
      <pubDate>Fri, 27 Oct 2023 15:43:42 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/unbundling-ai/</guid>
      <description>&lt;p&gt;A good question he poses:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is a science question, and a use-case question, but it’s also a product question - how do you present and package uncertainty?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I also liked this comparison:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But on the other hand, everyone in tech today is wrapping something else at some level. Snap uses Google Cloud for storage, but we don’t call it a ‘thin GCP Wrapper’.&lt;/p&gt;
&lt;/blockquote&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.ben-evans.com/benedictevans/2023/10/5/unbundling-ai&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Possibly Maybe poster by Dotto</title>
      <link>https://www.dotto.studio/store/p/possibly-maybe-screenprint</link>
      <pubDate>Wed, 18 Oct 2023 13:24:41 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/possibly-maybe/</guid>
      <description>&lt;p&gt;I really like this beautiful print for sale by &lt;a href=&#34;https://www.dotto.studio/&#34;&gt;Dotto&lt;/a&gt;:&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://www.dotto.studio/store/p/possibly-maybe-screenprint&#34;&gt;
  &lt;img
    alt=&#34;An poster with stylized text which read Possibly Maybe&#34;
    width=&#34;1200&#34;
    height=&#34;1200&#34;
    src=&#34;https://nathanherald.com/posts/links/possibly-maybe/image@2x_hu1934212420929312155.jpg&#34;&gt;
  &lt;/a&gt;
&lt;/figure&gt;


        
          &lt;p&gt;
            &lt;a href=&#34;https://www.dotto.studio/store/p/possibly-maybe-screenprint&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>NASA astronaut back on Earth after record-breaking spaceflight</title>
      <link>https://www.axios.com/2023/09/27/nasa-astronaut-frank-rubio-earth-return</link>
      <pubDate>Tue, 17 Oct 2023 11:07:09 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/nasa-astronaut-back-on-earth-after-record-breaking-spaceflight/</guid>
      <description>&lt;p&gt;👀&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;NASA astronaut Frank Rubio is back on Earth after a record-breaking 371 continuous days in space.&lt;/p&gt;
&lt;/blockquote&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.axios.com/2023/09/27/nasa-astronaut-frank-rubio-earth-return&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Fazerdaze – Bigger</title>
      <link>https://youtu.be/meyd4xT-09Y</link>
      <pubDate>Wed, 11 Oct 2023 13:55:02 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/fazerdaze-bigger/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/meyd4xT-09Y&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://youtu.be/meyd4xT-09Y&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Why Rappers Stopped Writing</title>
      <link>https://youtu.be/FSgl95BEmd0</link>
      <pubDate>Tue, 10 Oct 2023 10:50:30 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/why-rappers-stopped-writing/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/FSgl95BEmd0?&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;Found via &lt;a href=&#34;https://kottke.org/23/10/why-rappers-stopped-writing&#34;&gt;Kottke&lt;/a&gt;&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://youtu.be/FSgl95BEmd0&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>electric-sql – Local-first sync for Postgres from the inventors of CRDTs</title>
      <link>https://electric-sql.com/blog/2023/09/20/introducing-electricsql-v0.6</link>
      <pubDate>Mon, 09 Oct 2023 10:18:56 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/electric-sql/</guid>
      <description>&lt;p&gt;👀 Very interesting. And I like that the project is already &lt;a href=&#34;https://electric-sql.com/docs/api/ddlx&#34;&gt;trying to handle permissions and access&lt;/a&gt; – which is one of the hardest parts of two way replication of data. 🤔&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://electric-sql.com/blog/2023/09/20/introducing-electricsql-v0.6&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Desalination system could produce freshwater that is cheaper than tap water</title>
      <link>https://news.mit.edu/2023/desalination-system-could-produce-freshwater-cheaper-0927</link>
      <pubDate>Thu, 05 Oct 2023 23:21:51 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/desalination-system/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Engineers at MIT and in China are aiming to turn seawater into drinking water with a completely passive device that is inspired by the ocean, and powered by the sun.&lt;/p&gt;
&lt;/blockquote&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://news.mit.edu/2023/desalination-system-could-produce-freshwater-cheaper-0927&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Convenience and scams</title>
      <link>https://seths.blog/2023/09/convenience-and-scams/</link>
      <pubDate>Thu, 05 Oct 2023 11:41:18 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/convenience-and-scams/</guid>
      <description>&lt;p&gt;Found via &lt;a href=&#34;https://www.swiss-miss.com/2023/09/link-pack-129.html&#34;&gt;swissmiss&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://seths.blog/2023/09/convenience-and-scams/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Little Desktop Sculptures by Mohit Bhoite</title>
      <link>https://www.bhoite.com/sculptures/</link>
      <pubDate>Fri, 29 Sep 2023 11:42:50 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/tiny-sculptures/</guid>
      <description>&lt;p&gt;I found these lovely little sculptures &lt;a href=&#34;https://kottke.org/23/05/tiny-electronic-desktop-sculptures&#34;&gt;on kottke&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;They are so cute!&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;The Boron Lander sculpture&#34;
    width=&#34;360&#34;
    height=&#34;360&#34;
    src=&#34;https://nathanherald.com/posts/links/tiny-sculptures/image@2x_hu10523387906052787960.jpg&#34;&gt;
&lt;/figure&gt;


        
          &lt;p&gt;
            &lt;a href=&#34;https://www.bhoite.com/sculptures/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Oslo Transit Diagram</title>
      <link>https://t2design.no/oslo-transit-diagram-2022</link>
      <pubDate>Wed, 27 Sep 2023 10:32:23 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/oslo-transit-diagram/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Example close view of the transit diagram&#34;
    width=&#34;600&#34;
    height=&#34;336&#34;
    src=&#34;https://nathanherald.com/posts/links/oslo-transit-diagram/image@2x_hu15866113783471211527.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;This Oslo transit map by T2 Design is beautiful 😻&lt;/p&gt;
&lt;p&gt;There is a wonderful video about it too:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://youtu.be/tHQ5fOJkI-g?si=zWpdyKHK5eFTscz&#34;&gt;https://youtu.be/tHQ5fOJkI-g?si=zWpdyKHK5eFTscz&lt;/a&gt;&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/zWpdyKHK5eFTscz&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://t2design.no/oslo-transit-diagram-2022&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Water-Resistant “Research Notes” Notebook</title>
      <link>https://www.plain.tw/products/plain-research-notes-water-resistant</link>
      <pubDate>Mon, 25 Sep 2023 13:45:53 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/research-notes/</guid>
      <description>&lt;p&gt;I picked up this notebook a while back and I absolutely love it ❤️&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://www.plain.tw/products/plain-research-notes-water-resistant&#34;&gt;
  &lt;img
    width=&#34;780&#34;
    height=&#34;520&#34;
    src=&#34;https://nathanherald.com/posts/links/research-notes/notes_hu7757944443850613530.jpg&#34;&gt;
  &lt;/a&gt;
&lt;/figure&gt;


        
          &lt;p&gt;
            &lt;a href=&#34;https://www.plain.tw/products/plain-research-notes-water-resistant&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>new AsyncFunction – the async version of new Function</title>
      <link>https://picostitch.com/tidbits/2023/09/new-async-function-the-async-version-of-new-function/</link>
      <pubDate>Wed, 20 Sep 2023 20:56:58 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/new-async-function/</guid>
      <description>&lt;p&gt;TIL&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;AsyncFunction&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{}.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;constructor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fn&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;AsyncFunction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;await Promise.resolve(1)&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;🤯&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://picostitch.com/tidbits/2023/09/new-async-function-the-async-version-of-new-function/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Galileo Galilei’s First Drawings of the Moon After Seeing It Through the Telescope in 1609</title>
      <link>https://www.vintag.es/2023/09/galileo-moon-drawings.html</link>
      <pubDate>Mon, 18 Sep 2023 15:56:54 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/galileo-first-drawings-of-the-moon/</guid>
      <description>&lt;p&gt;Wow, incredible first sketch of the moon from Galileo.&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://www.vintag.es/2023/09/galileo-moon-drawings.html&#34;&gt;
  &lt;img
    alt=&#34;Sketch of the moon by Galileo&#34;
    width=&#34;462&#34;
    height=&#34;640&#34;
    src=&#34;https://nathanherald.com/posts/links/galileo-first-drawings-of-the-moon/moon_hu8438381619774224327.jpg&#34;&gt;
  &lt;/a&gt;
&lt;/figure&gt;


        
          &lt;p&gt;
            &lt;a href=&#34;https://www.vintag.es/2023/09/galileo-moon-drawings.html&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Learning about Web Components: Part 4 – Researching pre-rendering custom elements and/or SSR</title>
      <link>https://nathanherald.com/posts/web-components-4/</link>
      <pubDate>Sun, 17 Sep 2023 21:13:50 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/web-components-4/</guid>
      <description>&lt;p&gt;Now that I’m starting to get a handle on building custom elements which can do things dynamically in the browser, I also want to see how difficult it would be to implement &lt;a href=&#34;https://en.wikipedia.org/wiki/Server-side_scripting&#34;&gt;“server side rendering”&lt;/a&gt; for my custom elements. There is a noticeable delay when first loading my photo grid demo – the browser must download, parse, and run my javascript, instantiate my custom elements, they then do a simulated database query, and then finally the image grid can render.&lt;/p&gt;
&lt;p&gt;You can see the delay in this screen recording:&lt;/p&gt;
&lt;video controls src=&#34;https://nathanherald.com/posts/web-components-4/prev-demo.mov&#34; type=&#34;video/mp4&#34;&gt;
    
&lt;/video&gt;

&lt;p&gt;I’d prefer if the HTML returned from the server could already include the first version of the image grid, then the javascript could take over from there once it’s ready in the browser.&lt;/p&gt;
&lt;p&gt;The announcement of &lt;a href=&#34;https://webkit.org/blog/13851/declarative-shadow-dom/&#34;&gt;Declarative Shadow DOM in WebKit&lt;/a&gt; and an &lt;a href=&#34;https://developer.chrome.com/en/articles/declarative-shadow-dom/&#34;&gt;update about support in Chromium&lt;/a&gt; are what really got me started looking into web components again in the first place. The promise of being able to serve HTML which can be shown without any javascript means web components are finally ready for use 💪 Firefox has announced they also will be implementing the agreed upon declarative API, so the future is finally here.&lt;/p&gt;
&lt;p&gt;The first project I found which talked about declarative shadow DOM + SSR was &lt;a href=&#34;https://github.com/matthewp/ocean&#34;&gt;ocean&lt;/a&gt; – which provides “web component server-side rendering” in deno or a service worker. &lt;a href=&#34;https://www.npmjs.com/package/@lit-labs/ssr&#34;&gt;&lt;code&gt;lit-labs/ssr&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;https://docs.astro.build/en/guides/server-side-rendering/&#34;&gt;astro’s SSR&lt;/a&gt; are also very cool libraries.&lt;/p&gt;
&lt;p&gt;But those never really clicked for me. None of them seem to be both fully up-to-date with the latest specification + able to render any custom element I might build.&lt;/p&gt;
&lt;p&gt;It clicked for me today when I was rereading the &lt;a href=&#34;https://webkit.org/blog/13851/declarative-shadow-dom/&#34;&gt;two&lt;/a&gt; &lt;a href=&#34;https://developer.chrome.com/en/articles/declarative-shadow-dom/&#34;&gt;announcements&lt;/a&gt; and I finally noticed something I hadn’t noticed before.&lt;/p&gt;
&lt;p&gt;From &lt;a href=&#34;https://developer.chrome.com/en/articles/declarative-shadow-dom/&#34;&gt;the Chromium page&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Because the contents of a &lt;strong&gt;serialized&lt;/strong&gt; Declarative Shadow Root are entirely static…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From &lt;a href=&#34;https://webkit.org/blog/13851/declarative-shadow-dom/&#34;&gt;the WebKit page&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the following example, the outer template element contains an instance of some-component element and its shadow tree content is &lt;strong&gt;serialized&lt;/strong&gt; using declarative shadow DOM.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Both pages mention that declarative shadow DOM is supposed to be the serialized version of a living shadow root. This made more sense when I found an older page talking about &lt;a href=&#34;https://github.com/mfreed7/declarative-shadow-dom/blob/master/README.md#serialization&#34;&gt;a not-yet-standardized and chromium-only built-in function to serialize a custom element &lt;code&gt;getInnerHTML()&lt;/code&gt;&lt;/a&gt;. The example shows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;element&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;getInnerHTML&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;includeShadowRoots&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;I am more interested in more of an &lt;code&gt;outerHTML&lt;/code&gt; equivalent, but still I can work with this.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Calling &lt;code&gt;getInnerHTML()&lt;/code&gt; on the &lt;code&gt;&amp;lt;image-grid&amp;gt;&lt;/code&gt; in the &lt;a href=&#34;https://nathanherald.com/posts/web-components-3/demo.html&#34;&gt;demo page from part 3 of this series&lt;/a&gt; in Chrome returns:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;record-id&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;5&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c&#34;&gt;&amp;lt;!-- this shadowroot attribute is old and deprecated, see below --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;shadowroot&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;open&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nd&#34;&gt;host&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;align-items&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;center&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;flex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;flex-direction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;column&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;%&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;justify-content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;center&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;%&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;img&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;max-width&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;%&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;auto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;auto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;object-fit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;contain&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;overflow&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;hidden&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;name&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;block&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;min-height&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;min-content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;text-align&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;center&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;img&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;alt&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Photo of a lot of differently colored tulips&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;./five.jpg&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;1000&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;662&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;five.jpg&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;record-id&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;4&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c&#34;&gt;&amp;lt;!-- this shadowroot attribute is old and deprecated, see below --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;shadowroot&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;open&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nd&#34;&gt;host&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;align-items&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;center&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c&#34;&gt;/* ... */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;c&#34;&gt;/* ... */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;img&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;alt&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Photo through a hexagonal window into a tea room on a sunny day&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;./four.jpg&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;662&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;1000&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;four.jpg&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;shadowrootmode&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;open&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nd&#34;&gt;host&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;box-sizing&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;border-box&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;grid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;gap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;16&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;px&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;grid-template-columns&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fr&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;margin&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;auto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;max-width&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;600&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;px&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;padding&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;px&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;slot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;slot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;strong&gt;first thing&lt;/strong&gt; to unpack here is: Chrome’s non-yet-standardized &lt;code&gt;getInnerHTML()&lt;/code&gt; function is returning &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; elements with the old and deprecated &lt;code&gt;shadowroot&lt;/code&gt; attribute. So this function isn’t going to work out-of-the-box for me.&lt;/p&gt;
&lt;p&gt;Still, it never occurred to me that the DOM might know how to serialize itself. That SSR needn’t be so much about rendering templates or munging strings – instead be about building up a DOM to a state, then serializing it in a fully presentable way.  This feels &lt;em&gt;simpler&lt;/em&gt; and &lt;em&gt;easier&lt;/em&gt; for me to think about now.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;second thing&lt;/strong&gt; to notice is: when we serialize a shadow root, we must include a full template every time. That means if we have scoped &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;s in the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;, they will be repeated over and over for every instance of the custom element. We could handle this by extracting the CSS to a file and &lt;code&gt;@import&lt;/code&gt;ing it if we cared about optimizing things.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://developer.chrome.com/en/articles/declarative-shadow-dom/#server-rendering-with-style&#34;&gt;Chrome page&lt;/a&gt; says we don’t have to worry:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Styles specified this way are also highly optimized: if the same stylesheet is present in multiple Declarative Shadow Roots, it is only loaded and parsed once. The browser uses a single backing &lt;code&gt;CSSStyleSheet&lt;/code&gt; that is shared by all of the shadow roots, eliminating duplicate memory overhead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;strong&gt;third thing&lt;/strong&gt; to notice is: our custom element will have a &lt;code&gt;shadowRoot&lt;/code&gt; before our code calls &lt;code&gt;attachShadow()&lt;/code&gt;. We don’t technically have to worry about this: calling &lt;code&gt;attachShadow()&lt;/code&gt; will clear and return the existing &lt;code&gt;shadowRoot&lt;/code&gt; which was created by the HTML parser when it found the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; tag in our HTML.&lt;/p&gt;
&lt;p&gt;From this information, I figured I could quickly build my own serializer which outputs the up-to-date &lt;code&gt;&amp;lt;template shadowrootmode=open&amp;gt;&lt;/code&gt; attribute and works more like &lt;code&gt;outerHTML&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I found a &lt;a href=&#34;https://gist.github.com/developit/54f3e3d1ce9ed0e5a171044edcd0784f&#34;&gt;“&lt;code&gt;getInnerHTML()&lt;/code&gt; polyfill” written by &lt;code&gt;@developit&lt;/code&gt;&lt;/a&gt; (who works on preact and &lt;a href=&#34;https://mastodon.social/@developit&#34;&gt;I follow on mastodon&lt;/a&gt;). It’s a bit terse and took a bit for me to grasp, but it’s nice to see that it can be a really short and simple function.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://gist.github.com/myobie/071458ed72c395f20e97a81316a30e40&#34;&gt;Here is my version&lt;/a&gt;. It’s a very quick draft which works for me in all the current major browsers. It won’t work for every use case and isn’t ready for production… it is a proof-of-concept though.&lt;/p&gt;
&lt;p&gt;Here is the output from my serializer of the &lt;code&gt;&amp;lt;image-grid&amp;gt;&lt;/code&gt; custom element from the previous demo page:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-grid&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;aria-live&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;polite&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;role&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;region&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;aria-label&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Image grid&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;record-id&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;shadowrootmode&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;open&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c&#34;&gt;/* ... */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;img&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;alt&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Photo of a building on a sunny day&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;./one.jpg&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;1000&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;838&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;one.jpg&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;record-id&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;4&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c&#34;&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c&#34;&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;shadowrootmode&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;open&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;c&#34;&gt;/* ... */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;slot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;slot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-grid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;OK! I copied and pasted the serialized html from the browser’s console to a new &lt;code&gt;demo-serialized.html&lt;/code&gt; file, I did some quick hacking to make the components check if the &lt;code&gt;shadowRoot&lt;/code&gt; already exists, and now I have an example of what the resulting HTML could be like if I could do the serialization on the server.&lt;/p&gt;
&lt;p&gt;I recorded a screencast of the page loading now, which is basically immediate:&lt;/p&gt;
&lt;video controls src=&#34;https://nathanherald.com/posts/web-components-4/demo-ssr.mov&#34; type=&#34;video/mp4&#34;&gt;
    
&lt;/video&gt;

&lt;p&gt;The javascript kicks in and the images continue to re-sort every 10 seconds, just like before.&lt;/p&gt;
&lt;h2 id=&#34;whats-next&#34;&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;I don’t yet know how to run the serializer on the server – maybe one of those &lt;a href=&#34;https://github.com/WebReflection/linkedom&#34;&gt;“mock DOM libraries”&lt;/a&gt; could make it all work out. When I get some free time I’ll dive into that.&lt;/p&gt;
&lt;p&gt;If you have any feedback about any of this, if I made any mistakes, or if you just want to chat about web components or anything else, then &lt;a href=&#34;https://indieweb.social/@myobie&#34;&gt;find me on mastodon&lt;/a&gt; 👋&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Learning about Web Components: Part 3 – Define in a static initialization block</title>
      <link>https://nathanherald.com/posts/web-components-3/</link>
      <pubDate>Wed, 13 Sep 2023 09:50:09 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/web-components-3/</guid>
      <description>&lt;p&gt;&lt;!-- raw HTML omitted --&gt;TIL&lt;!-- raw HTML omitted --&gt; about: &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks&#34;&gt;static initialization blocks&lt;/a&gt; and how this is useful when defining a &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements&#34;&gt;custom element&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I knew about &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static&#34;&gt;static properties&lt;/a&gt;, but I never noticed one can do a &lt;code&gt;static {}&lt;/code&gt; block directly in a &lt;code&gt;class&lt;/code&gt; in javascript. Here is an example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;A&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;This output in the console immediately on page load&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// same as console.log(A)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The static block is run when the class itself is initialized, not when a new instance is constructed.&lt;/p&gt;
&lt;p&gt;Custom elements are defined as classes and need to also register themselves into the custom element registry, and static initialization blocks can help keep that registration code contained within the class:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ImageGrid&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;extends&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;HTMLElement&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;customElements&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;define&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;image-grid&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;constructor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;super&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;attachShadow&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;mode&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;open&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;slotAssignment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;manual&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// …
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Super cool 😎&lt;/p&gt;
&lt;p&gt;I’ve updated the &lt;a href=&#34;./demo.html&#34;&gt;demo page&lt;/a&gt; to use this technique and it works in all the modern browsers I’ve tried 💪&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>50 Short Rules for a Better Life (From the Stoics)</title>
      <link>https://www.swiss-miss.com/2023/09/50-short-rules-for-a-better-life-from-the-stoics.html</link>
      <pubDate>Tue, 12 Sep 2023 10:01:32 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/50-short-rules-for-a-better-life/</guid>
      <description>&lt;p&gt;…&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Be strict with yourself and tolerant with others.&lt;/li&gt;
&lt;li&gt;Learn something from everyone.&lt;/li&gt;
&lt;li&gt;Every person is an opportunity for kindness&lt;/li&gt;
&lt;li&gt;Make a little progress each day.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;…&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.swiss-miss.com/2023/09/50-short-rules-for-a-better-life-from-the-stoics.html&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>The Feather Atlas at the US Fish and Wildlife Service</title>
      <link>https://www.fws.gov/lab/featheratlas/</link>
      <pubDate>Mon, 11 Sep 2023 09:57:35 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/the-feather-atlas/</guid>
      <description>&lt;p&gt;It’s a lot of fun browsing through this online feather atlas. Like &lt;a href=&#34;https://www.fws.gov/lab/featheratlas/feather.php?Bird=WTTR_tail_adult_bluebk&#34;&gt;the white tailed tropic bird tail feathers&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.fws.gov/lab/featheratlas/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>“We’re All Just Temporarily Abled”</title>
      <link>https://blog.jim-nielsen.com/2023/temporarily-abled/</link>
      <pubDate>Tue, 05 Sep 2023 11:10:45 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/we-are-all-just-temporarily-abled/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;1 in 5 people currently have a disability. 100% of people will have some form of disability in their lifetime.&lt;/p&gt;
&lt;/blockquote&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://blog.jim-nielsen.com/2023/temporarily-abled/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Supavisor: Scaling Postgres to 1 Million Connections</title>
      <link>https://supabase.com/blog/supavisor-1-million</link>
      <pubDate>Mon, 04 Sep 2023 15:10:52 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/one-million-postgres-connections/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;One of the most widely-discussed shortcomings of Postgres is it&amp;rsquo;s connection system. Every Postgres connection has a reasonably high memory footprint, and determining the maximum number of connections your database can handle is a bit of an art.&lt;/p&gt;
&lt;p&gt;And so we&amp;rsquo;ve built Supavisor, a Postgres connection pooler that can handle millions of connections.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;👀🤔&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://supabase.com/blog/supavisor-1-million&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Learning about Web Components: Part 2 – Data access and UI updates</title>
      <link>https://nathanherald.com/posts/web-components-2/</link>
      <pubDate>Sun, 27 Aug 2023 13:02:22 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/web-components-2/</guid>
      <description>&lt;p&gt;I’ve finally spent a little more time learning enough to write a second post about &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Web_components&#34;&gt;Web Components&lt;/a&gt;. You can read &lt;a href=&#34;https://nathanherald.com/posts/web-components-1/&#34;&gt;Part 1 here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This time I focused on two questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sometimes the data I work with it local-only in a database in the browser – so how do I pass the database state down through the tree?&lt;/li&gt;
&lt;li&gt;When sorting or filtering items, how can I do that without completely re-rendering the items from scratch every time?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I have a new &lt;a href=&#34;./demo.html&#34;&gt;demo page&lt;/a&gt; for this post where you can see the working code: &lt;a href=&#34;./demo.html&#34;&gt;&lt;code&gt;demo.html&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s all in one HTML file, so view source and you’ll see everything there.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;modeling-the-database-state&#34;&gt;Modeling the database state&lt;/h2&gt;
&lt;p&gt;I didn’t want to actually setup a full &lt;code&gt;indexedDB&lt;/code&gt; database, so I made a fake &lt;code&gt;db&lt;/code&gt; variable and “query function” with a fake delay. Querying &lt;code&gt;indexedDB&lt;/code&gt; is always &lt;code&gt;async&lt;/code&gt; (annoying) so I wanted to model that.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;freeze&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;./one.jpg&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;freeze&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;simulatedSlowDBQuery&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Promise&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;setTimeout&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;200&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;shuffle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I decided that the &lt;code&gt;&amp;lt;image-grid&amp;gt;&lt;/code&gt; custom element can “contain” the list of image IDs it’s displaying. And then each &lt;code&gt;&amp;lt;image-cell&amp;gt;&lt;/code&gt; can “contain” the ID of the database record it represents. So this means each “component” requires a local database to render, which is fine for my use-case for this post.&lt;/p&gt;
&lt;p&gt;For this &lt;a href=&#34;./demo.html&#34;&gt;demo page&lt;/a&gt;, the page’s HTML just includes an empty &lt;code&gt;&amp;lt;image-grid&amp;gt;&amp;lt;/image-grid&amp;gt;&lt;/code&gt;. There is an “outside function” which will query the DB and provide the record IDs to the empty grid:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;grid&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;image-grid&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;queryThenRenderImages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;records&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;simulatedSlowDBQuery&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;grid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;recordIds&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;records&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// start with a query at boot
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;queryThenRenderImages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// query + shuffle every 10s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;setInterval&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;queryThenRenderImages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I also like that it would be pretty easy to consume an &lt;code&gt;AsyncIteratable&lt;/code&gt; to do a similar thing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;records&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;dbIterable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;grid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;recordIds&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;records&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;I do want to try to “server render” the grid and then hydrate it in the browser, that’s just too much for this blog post.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;reusing-image-cells-when-sorting&#34;&gt;Reusing &lt;code&gt;&amp;lt;image-cell&amp;gt;&lt;/code&gt;s when sorting&lt;/h2&gt;
&lt;p&gt;I’ve added a &lt;code&gt;shuffle()&lt;/code&gt; to the database query so I can emulate sorting and filtering. One of the things I wanted to solve was how to easily handle sorting with these custom elements. It turns out the secret is the &lt;code&gt;&amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://indieweb.social/@keithamus/110327619515521232&#34;&gt;&lt;code&gt;@keithamus&lt;/code&gt; tipped me off to this over on Mastodon&lt;/a&gt; &lt;em&gt;(sheesh, back on May 7, it certainly took me a while to act on his advice 😂)&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/assign&#34;&gt;slot is assignable&lt;/a&gt;, with something like: &lt;code&gt;slot.assign(...nodes)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Two things that took me a bit to figure out:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The shadow DOM needs to have its &lt;code&gt;slotAssignment&lt;/code&gt; set to &lt;code&gt;&#39;manual&#39;&lt;/code&gt;. This is how I’ve done that:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;this.attachShadow({ mode: &amp;#39;open&amp;#39;, slotAssignment: &amp;#39;manual&amp;#39; })
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A slot can only be assigned nodes that are children of the shadow DOM’s host. &lt;em&gt;Yep, that sentence is very specific, wordy, and was hard to be to fully parse and understand at first.&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is a visual example from Firefox that really helped me get it:&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of Firefox showing the DOM setup with the image-grid, it’s shadow-root with a slot filled with references to image-cells&#34;
    width=&#34;988&#34;
    height=&#34;913&#34;
    src=&#34;https://nathanherald.com/posts/web-components-2/firefox-screenshot@2x_hu10780048639828017216.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;In the above you can see a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;slot&amp;gt;&lt;/code&gt; is a child of the &lt;code&gt;#shadow-root&lt;/code&gt; element of the &lt;code&gt;&amp;lt;image-grid&amp;gt;&lt;/code&gt; element&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;image-cell&amp;gt;&lt;/code&gt; elements are all children of the &lt;code&gt;&amp;lt;image-grid&amp;gt;&lt;/code&gt; element, and not children of the &lt;code&gt;#shadow-root&lt;/code&gt; or &lt;code&gt;&amp;lt;slot&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;slot&amp;gt;&lt;/code&gt; shows that it has been “assigned” references to the &lt;code&gt;&amp;lt;image-cell&amp;gt;&lt;/code&gt; elements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since the &lt;code&gt;slot&lt;/code&gt; only holds references to &lt;code&gt;&amp;lt;image-cell&amp;gt;&lt;/code&gt;s, I can sort those references and re-assign without triggering a full re-paint of those &lt;code&gt;&amp;lt;image-cell&amp;gt;&lt;/code&gt; elements.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;At least, I think that is true.&lt;/em&gt; I haven’t been able to confirm that yet. I’ve tried profiling the page a few times, but I can’t seem to get &lt;em&gt;proof&lt;/em&gt; about what is exactly happening. If you know, please &lt;a href=&#34;mailto:myobie@duck.com&#34;&gt;email&lt;/a&gt; or &lt;a href=&#34;https://indieweb.social/@myobie&#34;&gt;@ me on mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A super nice thing is &lt;code&gt;slot.assignedNodes()&lt;/code&gt; will return the currently assigned nodes, so I don’t need to remember those inside the custom element and can just get them anytime I need them.&lt;/p&gt;
&lt;h2 id=&#34;attributechangedcallback&#34;&gt;&lt;code&gt;attributeChangedCallback&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;While the &lt;code&gt;recordIds&lt;/code&gt; of the &lt;code&gt;&amp;lt;image-grid&amp;gt;&lt;/code&gt; are entirely “private” to the class, I decided to implement the &lt;code&gt;record-id&lt;/code&gt; attribute of the &lt;code&gt;&amp;lt;image-cell&amp;gt;&lt;/code&gt; element and actually wire it up to fully work (I think).&lt;/p&gt;
&lt;p&gt;I can change the &lt;code&gt;record-id=&amp;quot;4&amp;quot;&lt;/code&gt; in the devtools and the element updates, which is pretty darn cool. But I’m not sure if this really is worth it or not. When passing data down through custom elements with attributes, everything has to be a string and I kinda hate that. So I’m not sure if trying to deal with serialization of these attributes is worth it to not yet.&lt;/p&gt;
&lt;p&gt;The “update function” for &lt;code&gt;&amp;lt;image-cell&amp;gt;&lt;/code&gt; reminds me of a &lt;code&gt;configureCell()&lt;/code&gt; function when using &lt;code&gt;UICollectionView&lt;/code&gt; or &lt;code&gt;UITableView&lt;/code&gt; in iOS apps:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;updateTemplate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// “query” for the record
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;record&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;recordId&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;find&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;id&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;recordId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// only update if it’s a different recordId
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;record&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;id&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!==&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lastRenderedRecordId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;shadowRoot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;replaceChildren&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// clear
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;record&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;template[data-name=&amp;#34;image-cell&amp;#34;]&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;shadowRoot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cloneNode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;img&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;shadowRoot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;img&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;pName&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;shadowRoot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;p.name&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;img&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;alt&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;record&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;description&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;img&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;src&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;record&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;src&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;img&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;width&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;record&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;width&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;img&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;height&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;record&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;height&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;nameParts&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;record&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;split&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;pName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;innerText&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;nameParts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;at&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lastRenderedRecordId&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;record&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I’d say I’m 80% happy with that function. I feel like there is still something I’m missing though 🤔&lt;/p&gt;
&lt;h2 id=&#34;use-cases&#34;&gt;Use cases&lt;/h2&gt;
&lt;p&gt;I could see this being useful for lots of situations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Search suggestions&lt;/li&gt;
&lt;li&gt;Search results&lt;/li&gt;
&lt;li&gt;Filtering any kind of list or grid&lt;/li&gt;
&lt;li&gt;Navigating between tabs&lt;/li&gt;
&lt;li&gt;Swapping out a page template by slotting in a header, sidebar/aside, and/or main content area with named slots&lt;/li&gt;
&lt;li&gt;What else can you think of?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;questions-still-to-answer&#34;&gt;Questions still to answer&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Is it common to support attributes with string serialization, or do most people leave those out and rely on javascript properties instead?&lt;/li&gt;
&lt;li&gt;What examples are there online of Web Components + IndexedDB?&lt;/li&gt;
&lt;li&gt;What are the accessibility implications of having elements in the DOM that are not slotted?&lt;/li&gt;
&lt;li&gt;Passing data down two levels is fine, but how do things look when there are many many levels?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;🫡&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>S.js and some thoughts about reactive state</title>
      <link>https://github.com/adamhaile/S</link>
      <pubDate>Thu, 17 Aug 2023 15:09:37 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/s-dot-js/</guid>
      <description>&lt;p&gt;I recently stumbled upon the &lt;a href=&#34;https://github.com/adamhaile/S&#34;&gt;S.js&lt;/a&gt; “reactive state library” and the related &lt;a href=&#34;https://github.com/adamhaile/surplus&#34;&gt;surplus&lt;/a&gt; web framework which builds upon it, and I wanted to recommend reading though it to any developer wanting to learn more about building web UIs with reactive state. The ideas I see in surplus are &lt;em&gt;so&lt;/em&gt; similar to the ideas I’ve been working towards with &lt;a href=&#34;https://new.space/&#34;&gt;with the Shareup team building new.space&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We’ve been iterating on how our application state is stored and how we interact with it in our UI components. 99% of the app’s state is stored in IndexedDB in the browser, so we really only need an in-memory mirror of the database’s state to hand to the UI when rendering.&lt;/p&gt;
&lt;p&gt;Our web app’s UI is written using &lt;a href=&#34;https://preactjs.com&#34;&gt;preact&lt;/a&gt;, and the default way to handle state in a contemporary (p)react app is to use &lt;code&gt;useState()&lt;/code&gt; or &lt;code&gt;useReducer()&lt;/code&gt;. &lt;em&gt;Sure, there is &lt;a href=&#34;https://redux.js.org&#34;&gt;redux&lt;/a&gt;, but the boilerplate of actions and all that is a lot for me.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;useState()&lt;/code&gt; is great for a web UI that loads some data, then displays it, and that’s it. It’s not great for a web UI that holds all of its data locally in indexedDB and wants the UI to react to any changes to the underlying storage over time. One either has to put a giant state/reducer way up in a root component or context, or one has lots of little states that all get lost anytime a large part of the UI changes.&lt;/p&gt;
&lt;p&gt;Navigating from one section of the app to another shouldn’t need to completely dump and reload from indexedDB – we just had that state and it doesn’t need to disappear when un-mounting. Yet, if one lifts the state up to a root-ish-level component, then the entire app UI will re-render for every little change to the underlying storage. Showing a progress meter is hilarious in that scenario.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/adamhaile/surplus&#34;&gt;surplus&lt;/a&gt; seems like the right idea: lift the state entirely out of the UI, make it reactive by default, and let the UI be a function of the reactive state. Only the parts of the app that use a part of the state need to re-render when that state changes.&lt;/p&gt;
&lt;p&gt;We are using, and really like, &lt;a href=&#34;https://preactjs.com/guide/v10/signals/&#34;&gt;signals&lt;/a&gt; which are surprisingly very similar to &lt;a href=&#34;https://github.com/adamhaile/S&#34;&gt;S.js&lt;/a&gt;… and &lt;a href=&#34;https://github.com/adamhaile/S&#34;&gt;S.js&lt;/a&gt; and &lt;a href=&#34;https://github.com/adamhaile/surplus&#34;&gt;surplus&lt;/a&gt; are both like 7–8 years old 🫢 I’m surprised I hadn’t come across it until now. Building high-performance web UIs is still a tough job all these years later 😅&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://github.com/adamhaile/S&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>She Was 300 Yards From the Atomic Bomb Center - and Survived Op-Docs</title>
      <link>https://www.youtube.com/watch?v=M64VRRiBhYI</link>
      <pubDate>Thu, 10 Aug 2023 10:00:27 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/akiko-takakura/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/M64VRRiBhYI&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;We saw Oppenheimer this past weekend, which was visually spectacular and somehow engaging for three full hours. Then yesterday I saw the above, and I haven’t been able to stop thinking about it.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=M64VRRiBhYI&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Ólafur Arnalds live from Hafursey, in Iceland for Cercle</title>
      <link>https://www.youtube.com/watch?v=bMCiAKNUpTY</link>
      <pubDate>Thu, 03 Aug 2023 10:53:31 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/%C3%B3lafur-arnalds-hafursey/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/bMCiAKNUpTY&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;This has been such a nice thing to have on while working today 😊&lt;/p&gt;
&lt;p&gt;Found via &lt;a href=&#34;https://www.swiss-miss.com/2023/07/olafur-arnalds-live-from-hafursey.html&#34;&gt;swiss miss&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=bMCiAKNUpTY&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Getting started with View Transitions on multi-page apps by Dave Rupert</title>
      <link>https://daverupert.com/2023/05/getting-started-view-transitions/</link>
      <pubDate>Fri, 21 Jul 2023 14:15:18 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/getting-started-with-view-transitions/</guid>
      <description>&lt;p&gt;I’ve been wanting to experiment with &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API&#34;&gt;View Transitions&lt;/a&gt;, and this post by Dave Rupert helped me get going.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://daverupert.com/2023/05/getting-started-view-transitions/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Omega Sports Apple Computer Sneakers • Size 10.5 • on Sotheby’s</title>
      <link>https://www.sothebys.com/en/buy/pdp/fashion/sneaker/lifestyle/_Omega-Sports-Apple-Computer-Sneakers-or-Size-10.5</link>
      <pubDate>Thu, 20 Jul 2023 22:40:06 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/vintage-omega-apple-sneakers/</guid>
      <description>&lt;p&gt;Only 50,000$ with &lt;em&gt;Complimentary shipping&lt;/em&gt; 😆&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://www.sothebys.com/en/buy/pdp/fashion/sneaker/lifestyle/_Omega-Sports-Apple-Computer-Sneakers-or-Size-10.5&#34;&gt;
  &lt;img
    alt=&#34;Photo of the Omega Sports Apple Computer Sneakers&#34;
    width=&#34;800&#34;
    height=&#34;575&#34;
    src=&#34;https://nathanherald.com/posts/links/vintage-omega-apple-sneakers/image_hu16507335472022672485.jpg&#34;&gt;
  &lt;/a&gt;
&lt;/figure&gt;


        
          &lt;p&gt;
            &lt;a href=&#34;https://www.sothebys.com/en/buy/pdp/fashion/sneaker/lifestyle/_Omega-Sports-Apple-Computer-Sneakers-or-Size-10.5&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Dive – Olivia Dean</title>
      <link>https://www.youtube.com/watch?v=YYlSa4M2zzM</link>
      <pubDate>Mon, 17 Jul 2023 13:34:57 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/dive-olivia-dean/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/YYlSa4M2zzM&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=YYlSa4M2zzM&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Embrace Complexity; Tighten Your Feedback Loops</title>
      <link>https://ferd.ca/embrace-complexity-tighten-your-feedback-loops.html</link>
      <pubDate>Wed, 21 Jun 2023 14:56:01 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/embrace-complexity/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Things that people think are useful are possibly going to happen even if you forbid them. If you forbid people from logging onto production hosts, and they truly think they&amp;rsquo;ll need it for emergency situations, they&amp;rsquo;ll make sure there&amp;rsquo;s still a way for it to happen, albeit under a different name.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The most basic point here is that you can’t expect to change the outcome of these small little decisions that accumulate all the time if you never address the pressures within the system that foster them.&lt;/p&gt;
&lt;/blockquote&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://ferd.ca/embrace-complexity-tighten-your-feedback-loops.html&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Today – Q</title>
      <link>https://music.apple.com/us/album/today/1650519297?i=1650519307</link>
      <pubDate>Wed, 07 Jun 2023 10:31:14 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/q-today/</guid>
      <description>&lt;p&gt;I just listened to Q for the first time today and I really am enjoying their music 🔊&lt;/p&gt;

&lt;iframe allow=&#34;autoplay *; encrypted-media *;&#34; frameborder=&#34;0&#34; height=&#34;150&#34; style=&#34;width:100%;max-width:660px;overflow:hidden;background:transparent;&#34; sandbox=&#34;allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation&#34; src=&#34;https://embed.music.apple.com/us/album/today/1650519297?i=1650519307&#34;&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://music.apple.com/us/album/today/1650519297?i=1650519307&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Nation of Language - Full Performance (Live on KEXP)</title>
      <link>https://www.youtube.com/watch?v=0PzIKBwrcDc</link>
      <pubDate>Mon, 05 Jun 2023 16:30:26 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/nation-of-language-live/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/0PzIKBwrcDc&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;I love the vibes 🔊&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=0PzIKBwrcDc&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Nopia</title>
      <link>https://youtu.be/Ivuy9QYLFVY</link>
      <pubDate>Wed, 31 May 2023 14:35:50 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/nopia/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/Ivuy9QYLFVY&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;This makes me want to get out my &lt;a href=&#34;https://www.ableton.com/en/push/&#34;&gt;Push&lt;/a&gt; and start making something…&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://youtu.be/Ivuy9QYLFVY&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Jordan Ward – CHERIMOYA</title>
      <link>https://youtu.be/sTOxZ5nPtKk</link>
      <pubDate>Thu, 18 May 2023 11:09:03 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/cherimoya-jordan-ward/</guid>
      <description>&lt;p&gt;The “retro aesthetic” is strong.&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/sTOxZ5nPtKk&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;It makes me feel old that the look of the camera equipment we had growing up is “retro” now.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://youtu.be/sTOxZ5nPtKk&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Rich Girl – Scary Pockets – featuring Arielle Kasnetz</title>
      <link>https://youtu.be/JQgBKh9MOs8</link>
      <pubDate>Mon, 15 May 2023 22:13:05 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/scary-pockets-rich-girl/</guid>
      <description>&lt;p&gt;I love this arrangement 👀❤️&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/JQgBKh9MOs8&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://youtu.be/JQgBKh9MOs8&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Great Wave Off Kanagawa As Pixel Art by James Weiner</title>
      <link>https://www.hypertalking.com/2023/05/08/1-bit-pixel-art-of-hokusais-the-great-wave-off-kanagawa/</link>
      <pubDate>Fri, 12 May 2023 10:20:29 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/the-great-wave/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;1 bit pixel art version of The Great Wave Off Kanagawa by James Weiner&#34;
    width=&#34;512&#34;
    height=&#34;342&#34;
    src=&#34;https://nathanherald.com/posts/links/the-great-wave/image_hu15499176959244435839.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;Unbelievable detail on this beautiful 1 bit pixel drawing of The Great Wave ❤️&lt;/p&gt;
&lt;p&gt;Found &lt;a href=&#34;https://kottke.org/23/05/great-wave-off-kanagawa-in-all-its-1-bit-pixelized-glory&#34;&gt;via Kottke&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.hypertalking.com/2023/05/08/1-bit-pixel-art-of-hokusais-the-great-wave-off-kanagawa/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>The Man from Laramie</title>
      <link>https://nathanherald.com/posts/the-man-from-laramie/</link>
      <pubDate>Mon, 08 May 2023 22:38:47 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/the-man-from-laramie/</guid>
      <description>&lt;p&gt;This past weekend I watched &lt;em&gt;The Man from Laramie&lt;/em&gt; in the &lt;a href=&#34;https://www.criterionchannel.com/anthony-mann-directs-james-stewart&#34;&gt;Criterion Channel’s &lt;em&gt;Anthony Mann Directs James Stewart&lt;/em&gt;&lt;/a&gt; collection. This was the fifth and final film Mann directed with Stewart as the star, and it was a great watch.&lt;/p&gt;
&lt;p&gt;Even with all their problems, I still love a good Western. I grew up watching them and they still captivate me. Especially one that has something interesting to say – and that’s what surprised me the most about this movie: it’s politics seem pretty progressive for 1955.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Photograph of my laptop screen playing the movie&#34;
    width=&#34;1200&#34;
    height=&#34;565&#34;
    src=&#34;https://nathanherald.com/posts/the-man-from-laramie/image@2x_hu11650024930135338257.jpg&#34;&gt;
&lt;/figure&gt;

&lt;details&gt;
  &lt;summary&gt;Some plot spoilers…&lt;/summary&gt;

  &lt;p&gt;The motivation of Stewart’s character was to hunt down and kill the person he felt responsible for his brother’s death. His brother was in the Cavalry and his entire group had been gunned down by nearby Native Americans.&lt;/p&gt;
&lt;p&gt;Yet, he didn’t blame nor was looking for the Native Americans, he was instead looking for who sold the “repeater rifles” to them. Since, to him, the person who sold the arms is just as guilty, or more, as the people who used them. The movie presents this as a very plausible and sensible motivation.&lt;/p&gt;
&lt;p&gt;This is currently being debated back home in the US: can gun manufactures be held liable for how their weapons are used? Now manufacture vs. dealer is different, sure, but it really seemed so “current” when watching this film.&lt;/p&gt;

&lt;/details&gt;

&lt;p&gt;&lt;em&gt;Also, this movie appeared to be “trying it’s best” for 1955 and not fall into every trope or harmful portrayal… but still it’s a Western from 1955 and does briefly present indigenous peoples in disparaging ways.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It has &lt;a href=&#34;https://www.rottentomatoes.com/m/man_from_laramie&#34;&gt;100% on the tomato meter&lt;/a&gt;, and it deserves a high score for sure. Stewart’s performance was captivating, and at times gut wrenching. I recommend giving it a watch. And I plan to watch the other four films in &lt;a href=&#34;https://www.criterionchannel.com/anthony-mann-directs-james-stewart&#34;&gt;the collection&lt;/a&gt;.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Learning about Web Components: Part 1 – Getting Started</title>
      <link>https://nathanherald.com/posts/web-components-1/</link>
      <pubDate>Sat, 06 May 2023 15:35:45 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/web-components-1/</guid>
      <description>&lt;p&gt;I’ve become interested in &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Web_components&#34;&gt;Web Components&lt;/a&gt; again. I’ve kept up with the smattering of standards that make up “Web Components” over the years, but since they were client-only and couldn’t really be server-rendered, they weren’t as interesting to me. Things have changed. &lt;a href=&#34;https://webkit.org/blog/13851/declarative-shadow-dom/&#34;&gt;declaritive shadow DOM&lt;/a&gt; makes server rendering possible and &lt;a href=&#34;https://github.com/11ty/webc&#34;&gt;webc&lt;/a&gt; seems like a neat way to do single file components (since &lt;a href=&#34;https://developer.chrome.com/blog/chrome-70-deps-rems/&#34;&gt;html imports&lt;/a&gt; didn’t make it, which was super sad).&lt;/p&gt;
&lt;p&gt;I have a good understanding of how to make &lt;em&gt;a&lt;/em&gt; web component, but I don’t really understand yet: how to compose a tree of web components, how to pass state changes down, is it easy and intuitive to bubble events up the tree, and how to do something similar to preact’s “render loop” or even if that type of loop would be desirable.&lt;/p&gt;
&lt;p&gt;I learn best by doing, so today I want to ease back into web components by making a simple grid of images. &lt;em&gt;Over time I’d like to enhance the grid to be reorderable, editable (add/remove images), and maybe even provide a custom name for each image… but that’s for another day.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I’ve written this part out before I started on the actual components, and we’ll see how far I get in 20 or so minutes…&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I managed to create an &lt;code&gt;image-grid&lt;/code&gt; component with &lt;code&gt;image-cell&lt;/code&gt; children which can load and show images in a nice grid.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./demo.html&#34;&gt;View the demo.html page here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The html looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-grid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;./one.jpg&amp;#34;&lt;/span&gt;   &lt;span class=&#34;na&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;3607&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;3024&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;./two.jpg&amp;#34;&lt;/span&gt;   &lt;span class=&#34;na&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;3448&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;4592&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;./three.jpg&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;4592&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;3448&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;./four.jpg&amp;#34;&lt;/span&gt;  &lt;span class=&#34;na&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;3264&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;4928&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;./five.jpg&amp;#34;&lt;/span&gt;  &lt;span class=&#34;na&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;4225&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;2798&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;./six.jpg&amp;#34;&lt;/span&gt;   &lt;span class=&#34;na&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;4928&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;3264&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-cell&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image-grid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a good start and I learned a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;&amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;&lt;/code&gt; to mean what &lt;code&gt;children&lt;/code&gt; means in preact works fine I think&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:host {}&lt;/code&gt; in the CSS is how to style the custom element itself, which is very handy&lt;/li&gt;
&lt;li&gt;The styles seem truly isolated. I tried to add &lt;code&gt;body { background-color: pink; }&lt;/code&gt; inside the custom element’s template and it had no affect. I also tried to style &lt;code&gt;img { outline: 5px solid purple; }&lt;/code&gt; in the parent &lt;code&gt;image-grid&lt;/code&gt; template and it didn’t affect the images inside the child &lt;code&gt;image-cell&lt;/code&gt; template. This is what I expected, but it’s the first time I’ve proven it to myself with a working example.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some notes for myself for next time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I think I need to break the &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; in the template out into a &lt;code&gt;.css&lt;/code&gt; file. If I server render this thing it’s going to duplicate the entire &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; for each invocation, including all the &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;s.&lt;/li&gt;
&lt;li&gt;I don’t like how separate and indirect the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; is from the &lt;code&gt;customElements.define(…)&lt;/code&gt; call.&lt;/li&gt;
&lt;li&gt;I don’t like how I’m cascading the &lt;code&gt;height&lt;/code&gt;, &lt;code&gt;src&lt;/code&gt;, &lt;code&gt;width&lt;/code&gt; attributes from the &lt;code&gt;image-cell&lt;/code&gt; to the &lt;code&gt;img&lt;/code&gt; tag right now. I’m not sure how to make it better, but it just feels weird to me.&lt;/li&gt;
&lt;li&gt;I always forget how to use css grid to layout images and keep their aspect ratios correct. It’s a battle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OK, that’s all for today. We’ll see when I take time to make the components more dynamic along with being server rendered.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Mighty Vibe</title>
      <link>https://www.mightyaudio.co.uk/products/mighty-vibe</link>
      <pubDate>Wed, 03 May 2023 21:44:11 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/mighty-vibe/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of the Mighty Vibe&#34;
    width=&#34;680&#34;
    height=&#34;540&#34;
    src=&#34;https://nathanherald.com/posts/links/mighty-vibe/image@2x_hu13313131489145579412.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;A very cute music player that reminds me of the iPod Shuffle. I absolutely loved my iPod Shuffle back in the day, and it’s very appealing to just have a dedicated “play me some great music” device.&lt;/p&gt;
&lt;p&gt;Found &lt;a href=&#34;https://techhub.social/@dancounsell/110293930020630674&#34;&gt;via @dancounsell@techhub.social on Mastodon&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.mightyaudio.co.uk/products/mighty-vibe&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Generate a QR Code in your shell with npx, qrcode-svg, and svgo</title>
      <link>https://mastodon.social/@pepelsbey/110222198088406039</link>
      <pubDate>Mon, 01 May 2023 17:54:58 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/cli-qrcode/</guid>
      <description>&lt;p&gt;A very interesting shell script snippet from Vadim to quickly generate a QR Code in your shell. A slightly modified of their original script is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; qr&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  npx qrcode-svg --join --no-prettify --viewbox --padding &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt; --ecl L --force --output &lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;.svg &lt;span class=&#34;nv&#34;&gt;$2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  npx svgo --quiet --precision &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;.svg
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  open &lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;.svg
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
        
          &lt;p&gt;
            &lt;a href=&#34;https://mastodon.social/@pepelsbey/110222198088406039&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Testing the Web Share API</title>
      <link>https://www.raymondcamden.com/2023/04/20/testing-the-web-share-api</link>
      <pubDate>Fri, 28 Apr 2023 15:08:40 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/web-share-api/</guid>
      <description>&lt;p&gt;Raymond Camden has a good overview of how weird the Web Share API works on different platforms. Working on &lt;a href=&#34;https://shareup.app/&#34;&gt;Shareup’s apps&lt;/a&gt; we’ve also seen how strange the native share API can be, and we really only use it on mobile platforms right now.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.raymondcamden.com/2023/04/20/testing-the-web-share-api&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Urth Explorer Filter Kit</title>
      <link>https://urth.co/products/the-explorer-filter-kit</link>
      <pubDate>Wed, 26 Apr 2023 23:02:45 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/urth-explorer-filter-kit/</guid>
      <description>&lt;p&gt;👀&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;The Urth Explorer Filter Kit showing the packaging and the filters&#34;
    width=&#34;400&#34;
    height=&#34;246&#34;
    src=&#34;https://nathanherald.com/posts/links/urth-explorer-filter-kit/image@2x_hu4624238208200562249.jpg&#34;&gt;
&lt;/figure&gt;


        
          &lt;p&gt;
            &lt;a href=&#34;https://urth.co/products/the-explorer-filter-kit&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>GaeaStar</title>
      <link>https://gaeastar.com</link>
      <pubDate>Mon, 24 Apr 2023 23:15:05 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/gaea/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the website for GaeaStar showing a clay bowl&#34;
    width=&#34;1200&#34;
    height=&#34;861&#34;
    src=&#34;https://nathanherald.com/posts/links/gaea/screenshot@2x_hu15191385958461958197.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;I’m &lt;strong&gt;very interested&lt;/strong&gt; in these sustainable single use bowls and cups. I am going to have to go to &lt;a href=&#34;https://www.instagram.com/p/CopUsu_Mp9H/&#34;&gt;Rosa Canina&lt;/a&gt; to get some ice cream and try one out. I wonder how if it will feel stable and solid, especially with something like ice cream or coffee 🤔&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://gaeastar.com&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>The original Wilhelm Scream recording session</title>
      <link>https://blog.freesound.org/?p=1515</link>
      <pubDate>Fri, 21 Apr 2023 10:25:56 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/wilhelm-scream/</guid>
      <description>&lt;p&gt;I was really intrigued to hear the original recording of the famous &lt;a href=&#34;https://en.wikipedia.org/wiki/Wilhelm_scream&#34;&gt;Wilhelm Scream&lt;/a&gt;, including the context and direction given to the performer. Craig Smith is an audio preservationist and took great pains and care to get the audio from the &lt;em&gt;Sunset Editorial Sound Effects Library&lt;/em&gt; collection of tapes, which were used in many TV since the 60’s – the most famous sound being the &lt;a href=&#34;https://en.wikipedia.org/wiki/Wilhelm_scream&#34;&gt;Wilhelm Scream&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Craig uploaded the sounds to &lt;a href=&#34;https://freesound.org/people/craigsmith/sounds/675810/&#34;&gt;freesound.org&lt;/a&gt; under a public domain license, and I used &lt;code&gt;ffmpeg&lt;/code&gt; to transform the &lt;code&gt;.wav&lt;/code&gt; file to a &lt;code&gt;.mp3&lt;/code&gt; so that you can play the original Wilhelm Scream recording session right here:&lt;/p&gt;
&lt;audio controls src=&#34;https://nathanherald.com/posts/links/wilhelm-scream/scream.mp3&#34;&gt;
    &lt;a href=&#34;https://freesound.org/people/craigsmith/sounds/675810/&#34;&gt;Listen over at freesound.org&lt;/a&gt;
&lt;/audio&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Converted with the terminal command:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ffmpeg -i scream.wav -vn -ar 48000 -ac 1 -b:a 192k scream.mp3&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Found via &lt;a href=&#34;https://waxy.org/2023/04/restoring-the-original-wilhelm-scream-recording-session/&#34;&gt;waxy.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://blog.freesound.org/?p=1515&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Moving, a Short Film</title>
      <link>https://www.criterionchannel.com/videos/moving</link>
      <pubDate>Thu, 20 Apr 2023 10:14:35 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/moving/</guid>
      <description>&lt;p&gt;I really enjoyed this short film by Adinah Dancyger that I watched on &lt;a href=&#34;https://www.criterionchannel.com/videos/moving&#34;&gt;the Criterion Channel online&lt;/a&gt;. If you don’t have a sub to Crtierion, then you can find it on &lt;a href=&#34;https://www.shortoftheweek.com/2021/03/12/moving/&#34;&gt;Short of the Week&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.criterionchannel.com/videos/moving&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>An Interview With Massimo Vitali</title>
      <link>https://www.present.xxx/massimo-vitali</link>
      <pubDate>Wed, 19 Apr 2023 12:28:40 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/interview-with-massimo-vitali/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;How do you manage your personal and your work life? Is there a clear separation between the two for you?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Massimo: Always separate for me. The studio is not far from my house, a nice place where I can go by bike. I remember years ago I had my studio and my home in the same place … impossible! The simple act of riding my bicycle for four minutes changes my mind into studio mode and when I go home I think about home.&lt;/p&gt;
&lt;/blockquote&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.present.xxx/massimo-vitali&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Dreamy Photography Settings</title>
      <link>https://www.youtube.com/watch?v=_kkU0Tc1cP0</link>
      <pubDate>Tue, 18 Apr 2023 21:03:33 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/dreamy-photography-settings/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/_kkU0Tc1cP0&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;🤔 I’ve been trying to lean more about how to develop my photos and this really caught my eye.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=_kkU0Tc1cP0&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>VIKING Trailer</title>
      <link>https://www.youtube.com/watch?v=a_mdIEDJwNw</link>
      <pubDate>Mon, 17 Apr 2023 16:19:58 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/viking-trailer/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/a_mdIEDJwNw&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;👀 This looks so interesting… 🤔&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=a_mdIEDJwNw&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Ship the wrong thing to learn what’s right</title>
      <link>https://nathanherald.com/posts/ship-the-wrong-thing/</link>
      <pubDate>Fri, 14 Apr 2023 12:23:55 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/ship-the-wrong-thing/</guid>
      <description>&lt;p&gt;A post I saw on Mastodon reminded me of an important principle for iterating and learning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Chroningham&amp;rsquo;s law states: &amp;ldquo;the best way to get the right answer on the internet is not to ask a question; it&amp;rsquo;s to post the wrong answer.&amp;rdquo;
– &lt;a href=&#34;https://tinnies.club/@jameswhite&#34;&gt;https://tinnies.club/@jameswhite&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve often used this method to find my way forward. If I don’t know how to make something visually nice, I’ll make something ugly and then ask for feedback. &lt;strong&gt;People find it much easier to tell you how to make something better from a certain point&lt;/strong&gt; vs. providing feedback on how something theoretically might look. Write bad code. Draw bad pictures. Ship an ugly prototype. Getting the wrong thing out can help guide you to the right thing.&lt;/p&gt;
&lt;p&gt;I recommend trying it out. Make something bad, and then ask for help or advice. You might be surprised how quickly you can make progress using this methodology.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Who invented vector clocks?</title>
      <link>https://decomposition.al/blog/2023/04/08/who-invented-vector-clocks/</link>
      <pubDate>Wed, 12 Apr 2023 11:03:24 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/who-invented-vector-clocks/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;…vector clocks (and matrix clocks, even) were originally thought up and used by a bunch of people in the early-to-mid-80s…&lt;/p&gt;
&lt;/blockquote&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://decomposition.al/blog/2023/04/08/who-invented-vector-clocks/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Gawthorne’s Hut</title>
      <link>https://wilgowrah.com.au/gawthornes-hut-mudgee/</link>
      <pubDate>Tue, 11 Apr 2023 08:10:10 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/gawthornes-hut/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of Gawthorne’s Hut&#34;
    width=&#34;1200&#34;
    height=&#34;800&#34;
    src=&#34;https://nathanherald.com/posts/links/gawthornes-hut/image@2x_hu4882972665174005760.jpg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;This is a beautiful little hut 🤩 in &lt;a href=&#34;https://www.australia.com/en/places/sydney-and-surrounds/guide-to-mudgee.html&#34;&gt;Mudgee Australia&lt;/a&gt; that is listed on AirBnB. Designed by &lt;a href=&#34;https://www.caarch.com.au&#34;&gt;Cameron Anderson&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Found &lt;a href=&#34;https://thedesignfiles.net/2023/02/stays-gawthornes-hut-cameron-anderson-architects/&#34;&gt;via The Design Files&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://wilgowrah.com.au/gawthornes-hut-mudgee/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>You Are Not Okay and Tomorrow Will Come</title>
      <link>https://humanparts.medium.com/you-are-not-ok-and-tomorrow-will-come-23d7c6fec171</link>
      <pubDate>Wed, 05 Apr 2023 11:22:27 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/you-are-not-okay-and-tomorrow-will-come/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Everything is terrible and I can still change the clock battery.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A short read which really resonated with me.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://humanparts.medium.com/you-are-not-ok-and-tomorrow-will-come-23d7c6fec171&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Art by Nathan Walsh</title>
      <link>https://www.nathanwalsh.net</link>
      <pubDate>Tue, 04 Apr 2023 10:41:05 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/nathan-walsh/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Delmonico&amp;#39;s by Nathan Walsh&#34;
    width=&#34;540&#34;
    height=&#34;513&#34;
    src=&#34;https://nathanherald.com/posts/links/nathan-walsh/image@2x_hu4847096052171795296.jpg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;Nathan Walsh’s work is stunning. Checkout &lt;a href=&#34;https://www.nathanwalsh.net&#34;&gt;his site&lt;/a&gt; to see more, and give him &lt;a href=&#34;https://www.instagram.com/nathan_walsh_artist/&#34;&gt;a follow on Instagram @nathan_walsh_artist&lt;/a&gt;. I cannot get over how detailed, meticulous, and hyper-realistic his work is 🤩 Unbelievable.&lt;/p&gt;
&lt;p&gt;Found &lt;a href=&#34;https://www.thisiscolossal.com/2023/03/nathan-walsh-oil-paintings/&#34;&gt;via Colossal&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.nathanwalsh.net&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Slow Learning</title>
      <link>https://www.swiss-miss.com/2023/03/slow-learning.html</link>
      <pubDate>Mon, 03 Apr 2023 14:02:01 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/slow-learning/</guid>
      <description>&lt;p&gt;This “Manifesto for Slow Learning” linked to by &lt;a href=&#34;https://www.swiss-miss.com/2023/03/slow-learning.html&#34;&gt;swissmiss&lt;/a&gt; is really great. The ten points of the “Bill of Rights” are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Focus on direction, not destination&lt;/li&gt;
&lt;li&gt;Raise your hand&lt;/li&gt;
&lt;li&gt;Learn at your own pace&lt;/li&gt;
&lt;li&gt;Unplug&lt;/li&gt;
&lt;li&gt;Change your learning path (and mind)&lt;/li&gt;
&lt;li&gt;Take a break&lt;/li&gt;
&lt;li&gt;Make mistakes&lt;/li&gt;
&lt;li&gt;Leave it unfinished&lt;/li&gt;
&lt;li&gt;Unlearn and forget&lt;/li&gt;
&lt;li&gt;Slow down&lt;/li&gt;
&lt;/ol&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.swiss-miss.com/2023/03/slow-learning.html&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>The Art of the Copyist by The Met</title>
      <link>https://www.youtube.com/watch?v=NRjwG1Ru7N8</link>
      <pubDate>Fri, 31 Mar 2023 11:24:35 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/the-copyist/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/NRjwG1Ru7N8&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=NRjwG1Ru7N8&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Polaroid Lab</title>
      <link>https://www.polaroid.com/products/polaroid-lab</link>
      <pubDate>Wed, 29 Mar 2023 12:05:34 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/polaroid-lab/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Polaroid Lab&#34;
    width=&#34;1000&#34;
    height=&#34;1000&#34;
    src=&#34;https://nathanherald.com/posts/links/polaroid-lab/image@2x_hu15317124505664953108.jpg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;❤️  such a cute little photo printer!&lt;/p&gt;
&lt;p&gt;Via &lt;a href=&#34;https://www.swiss-miss.com/2019/11/polaroid-lab.html&#34;&gt;swissmiss&lt;/a&gt;&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.polaroid.com/products/polaroid-lab&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Inside Out. Understanding the art of furniture making</title>
      <link>https://www.smb.museum/en/exhibitions/detail/inside-out-understanding-the-art-of-furniture-making/</link>
      <pubDate>Tue, 28 Mar 2023 11:10:45 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/inside-out-furniture-making/</guid>
      <description>&lt;p&gt;I was reminded today of this furniture exhibit I saw back in 2019, and how amazingly nested the compartments were in some of the desks and cabinets. In this video they show you all the little nested areas in a piece:&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/yqGpamyu6rg&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;It was amazing to see the pieces in real life. I recommend checking out a similar exhibit if one ever shows up near you.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.smb.museum/en/exhibitions/detail/inside-out-understanding-the-art-of-furniture-making/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Men I Trust - Show Me How (garage session)</title>
      <link>https://www.youtube.com/watch?v=cpU7Yrl-AwM</link>
      <pubDate>Mon, 27 Mar 2023 13:29:43 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/men-i-trust/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/cpU7Yrl-AwM&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=cpU7Yrl-AwM&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Olamide Ogunade’s art</title>
      <link>https://olamideogunade.com/</link>
      <pubDate>Wed, 22 Mar 2023 10:45:30 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/olamide-ogunade/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Painting of a woman on a blue stool in a bright, rainbox shirt, in front of a red wall with bubbles floating&#34;
    width=&#34;600&#34;
    height=&#34;791&#34;
    src=&#34;https://nathanherald.com/posts/links/olamide-ogunade/art@2x_hu3478560948676606068.jpg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;I love these arresting images with the playful bubbles floating throughout. This is from their &lt;a href=&#34;https://olamideogunade.com/myart&#34;&gt;“Artist’s Statement” page&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My art is an expression of figurative images using charcoal and colors to give it a contemporary look. It talks about situations in African society. The skin is an essential thing. I love to create it by focusing on such details as contrast. The dark hue of the skin is usually accompanied with vibrant colors for that complementary yinyang color contrast. I then sum up the work with bubbles – this captures the temporariness of the pressures we face.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Found via &lt;a href=&#34;https://www.booooooom.com/2023/02/27/artist-spotlight-olamide-ogunade/&#34;&gt;booooooom&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://olamideogunade.com/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Behringer TORO</title>
      <link>https://www.youtube.com/watch?v=rDFyk_jKFLk</link>
      <pubDate>Tue, 21 Mar 2023 10:45:30 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/toro/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/rDFyk_jKFLk&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;These wonderfully crunchy bass sounds 🧑‍🍳😗&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=rDFyk_jKFLk&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Chompi</title>
      <link>https://www.youtube.com/watch?v=JAmw-TTG2Ds</link>
      <pubDate>Mon, 20 Mar 2023 10:50:30 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/chompi/</guid>
      <description>&lt;p&gt;I love this cute little tape music instrument named Chompi ❤️ I really want to get into these types of instruments more.&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/JAmw-TTG2Ds&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=JAmw-TTG2Ds&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Enhancing Directed Content Sharing on the Web (PDF)</title>
      <link>https://hci.stanford.edu/publications/2010/feedme/feedme-chi2010.pdf</link>
      <pubDate>Thu, 16 Mar 2023 11:57:04 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/feed-me-paper/</guid>
      <description>&lt;p&gt;This paper about sharing web content is a good read. They created a browser extensions named FeedMe, where:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;the user mediates recommendations not for themselves, but on behalf of someone they know…
FeedMe’s users act as gatekeepers for someone else…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of their conclusions really resonated with me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Link Sharing Is Burdensome When It Is a Repetitive Firehose&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That feels very true for me. It’s a good paper, give it a read.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://hci.stanford.edu/publications/2010/feedme/feedme-chi2010.pdf&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Largest known prime number</title>
      <link>https://en.wikipedia.org/wiki/Largest_known_prime_number</link>
      <pubDate>Wed, 15 Mar 2023 12:20:55 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/largest-known-prime-number/</guid>
      <description>
2&lt;sup&gt;82,589,933&lt;/sup&gt; − 1


&lt;p&gt;Which has 24,862,048 digits.&lt;/p&gt;
&lt;p&gt;🤯&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(I like how the Wikipedia page says “24,861,808 digits skipped” 😆)&lt;/em&gt;&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://en.wikipedia.org/wiki/Largest_known_prime_number&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>GPT-4</title>
      <link>https://openai.com/research/gpt-4</link>
      <pubDate>Wed, 15 Mar 2023 11:13:46 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/gpt4/</guid>
      <description>&lt;p&gt;The latest update to Open AI’s preeminent model seems very impressive. It’s better at exams and it can now take images as input, along with text prompts.&lt;/p&gt;
&lt;p&gt;Up until now, I haven’t felt completely &lt;em&gt;mystified&lt;/em&gt; about ChatGPT, and what it’s doing. It has been easy to tell myself “it’s just choosing the next word using a very fancy weighted decision network,” &lt;em&gt;until I saw this example:&lt;/em&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Open AI’s GPT-4 takes a scan of a paper as input, and can answer the question asked in the paper which requires interpreting a diagram&#34;
    width=&#34;909&#34;
    height=&#34;702&#34;
    src=&#34;https://nathanherald.com/posts/links/gpt4/screenshot@2x_hu5099530410884819835.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;It’s given a scan of a page in French, with a setup, a diagram, and then two questions. The text prompt provided asks it to answer one of those questions from the scan. They didn’t tell it what the question was in the text prompt, they only provided “Answer question I.1.a.”&lt;/p&gt;
&lt;p&gt;This feels like a slight-of-hand magic trick which I can’t figure out. It doesn’t feel like it’s &lt;em&gt;just predicting the next word,&lt;/em&gt; even if that is true. It feels like &lt;strong&gt;we are in for some big changes in the next year, the next five years.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When large multimodal models can ace any exam, when they can offer very good advice for most problems a person might face, &lt;strong&gt;the definition of expertise will need to change.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Would you want to hire a human who got a B throughout their university time and is a “expert”, or would you want to hire a human who didn’t go to university and has a large model sidekick to guide them? Who would really be the expert? Even if the “traditional expert” also had a large model sidekick to help them, how much more of an expert are they really? Will the delta be that large?&lt;/p&gt;
&lt;p&gt;Expertise might end up being not that important. Instead, it feels like what will be important will be things which are: &lt;em&gt;&lt;a href=&#34;https://www.websters1913.com/words/Manual&#34;&gt;manual&lt;/a&gt;.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Of or pertaining to the hand&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Performed by a person using physical as contrasted with mental effort&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;“Mental effort” might be a lot less important soon. “Manual effort” will be special, and maybe more rare.&lt;/p&gt;
&lt;p&gt;When photography came onto the scene, people didn’t 100% stop painting. It became special, and rarer. Most people today take selfies, or &lt;em&gt;maybe&lt;/em&gt; they hire a portrait photographer, they rarely hire a portrait painter.&lt;/p&gt;
&lt;p&gt;Large multimodal models feel like nascent “photography for mental effort.”&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=jPhJbKBuNnA&#34;&gt;Tom Scott made a related video&lt;/a&gt; a month ago, primarily asking the question “where are we on the S curve for AI tech?”&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/jPhJbKBuNnA&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;It certainly feels like we are at the start of S curve today.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;I often consult &lt;a href=&#34;https://www.websters1913.com/words/Manual&#34;&gt;Webster’s Unabridged 1913 Dictionary&lt;/a&gt; – it always helps me think more broadly and clearly.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://openai.com/research/gpt-4&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>MathGPT</title>
      <link>https://mathgpt.streamlit.app</link>
      <pubDate>Tue, 14 Mar 2023 12:20:55 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/math-gpt/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Use GPT3 to solve math problems, and get the code behind each solution!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is pretty cool, it asks &lt;a href=&#34;https://en.wikipedia.org/wiki/ChatGPT&#34;&gt;ChatGPT&lt;/a&gt; to write python code to solve math problems. It is really pretty amazing to see.&lt;/p&gt;
&lt;p&gt;I did wonder if it was actually doing the calculation or if it had memorized a lot of calculations, so I did a test that you should not do.&lt;/p&gt;
&lt;p&gt;I asked it to find the 67,879,887,654,898th prime number. I feel bad, because it tried to find the prime, and the entire service went down. It was down for about 40 minutes. It did not find the prime.&lt;/p&gt;
&lt;p&gt;I’m not sure how one could protect against &lt;a href=&#34;https://en.wikipedia.org/wiki/Denial-of-service_attack&#34;&gt;DOS attacks&lt;/a&gt; when the goal is to have a ML model write a computer program, and then execute it. 🤔 One would have real uptime problems without any protections, but maybe that doesn’t matter if the system can timeout anything and just restart itself.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://mathgpt.streamlit.app&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Cargobox by Meelis Lillemets</title>
      <link>https://lmets.carbonmade.com/cargobox</link>
      <pubDate>Mon, 13 Mar 2023 13:19:36 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/cargobox/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of the Cargobox&#34;
    width=&#34;960&#34;
    height=&#34;540&#34;
    src=&#34;https://nathanherald.com/posts/links/cargobox/image@2x_hu6511594841654196092.jpg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;I love this absolute unit of a truck! ❤️&lt;/p&gt;
&lt;p&gt;It’s so cute, and it’s a great idea to try out different designs of electric cars for different purposes. At low speeds, the vehicle doesn’t need to focus as much on aerodynamics, and can prioritize the cargo space.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://lmets.carbonmade.com/cargobox&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>TIL how to allow notifications when screen recording on macOS</title>
      <link>https://nathanherald.com/posts/til/how-to-allow-notifications-when-recording-the-screen/</link>
      <pubDate>Sat, 11 Mar 2023 12:03:22 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/how-to-allow-notifications-when-recording-the-screen/</guid>
      <description>&lt;p&gt;Today I was trying to make a screen recording on my Mac of some notifications coming in, but anytime I started recording the screen the notifications would no longer show up. When I stopped recording, there they were 😡&lt;/p&gt;
&lt;p&gt;There is a setting in System Settings to “Allow notifications when mirroring or sharing the display” which is off by default. That makes perfect sense, if you are presenting or sharing your screen you don’t want people to see your notifications.&lt;/p&gt;
&lt;p&gt;So I toggled it to on, was able to do my screen recording of the notifications, and then toggled it back to off.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the Notifications settings on macOS with “Allow notifications when mirroring or sharing the display” highlighted&#34;
    width=&#34;481&#34;
    height=&#34;224&#34;
    src=&#34;https://nathanherald.com/posts/til/how-to-allow-notifications-when-recording-the-screen/screenshot@2x_hu39299073508008865.png&#34;&gt;
&lt;/figure&gt;


        
      </description>
    </item>
    <item>
      <title>TIL about Beamer for LaTeX</title>
      <link>https://en.wikipedia.org/wiki/Beamer_(LaTeX)</link>
      <pubDate>Thu, 09 Mar 2023 19:20:36 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/beamer/</guid>
      <description>&lt;p&gt;I’ve been super interested in using &lt;a href=&#34;https://daringfireball.net/projects/markdown/&#34;&gt;markdown&lt;/a&gt; to make slides, and especially using &lt;a href=&#34;https://ia.net/presenter&#34;&gt;iA Presenter&lt;/a&gt;, and so I was thrilled to learn about &lt;a href=&#34;https://en.wikipedia.org/wiki/Beamer_(LaTeX)&#34;&gt;Beamer&lt;/a&gt;. I don’t totally get it yet, but it’s super cool to dig into and look through the examples in &lt;a href=&#34;https://github.com/josephwright/beamer&#34;&gt;the repo&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://en.wikipedia.org/wiki/Beamer_(LaTeX)&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Notes art</title>
      <link>https://notes.art/</link>
      <pubDate>Wed, 08 Mar 2023 17:24:34 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/notes-art/</guid>
      <description>&lt;p&gt;I love these beautiful pieces done by Chris Silverman in the Notes.app on an iPhone. Constraints can really push someone to make impressive and beautiful things.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;An illustration of an original Mac computer&#34;
    width=&#34;550&#34;
    height=&#34;923&#34;
    src=&#34;https://nathanherald.com/posts/links/notes-art/mac@2x_hu10015784582086648783.jpg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;Found &lt;a href=&#34;https://kottke.org/23/03/notes-art&#34;&gt;via Kottke&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://notes.art/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Patch Notes: Hélène Vogelsinger</title>
      <link>https://youtu.be/kYxheEGl2oM</link>
      <pubDate>Tue, 07 Mar 2023 10:17:58 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/patch-notes/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/kYxheEGl2oM&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;100% life goal to have modular synths, in a rundown country house, and make wonderful music.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://youtu.be/kYxheEGl2oM&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>The Full Frod Story! Building a frog house for a Frod.</title>
      <link>https://youtu.be/JbxE9myZrsg</link>
      <pubDate>Mon, 06 Mar 2023 23:19:35 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/frods/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/JbxE9myZrsg&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;This is lovely 🐸&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://youtu.be/JbxE9myZrsg&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>The Shortcut</title>
      <link>https://www.bewitched.com/art/shortcut/</link>
      <pubDate>Fri, 03 Mar 2023 08:58:58 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/the-shortcut/</guid>
      <description>&lt;p&gt;I don’t think I can include a still screenshot that conveys how satisfying this animation is. &lt;a href=&#34;https://www.bewitched.com/art/shortcut/&#34;&gt;The Shortcut&lt;/a&gt; by Martin Wattenberg is a wonderful thing to watch play out.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.bewitched.com/art/shortcut/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>I really love these paintings by John Gannam</title>
      <link>https://nathanherald.com/posts/john-gannam/</link>
      <pubDate>Thu, 02 Mar 2023 21:23:04 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/john-gannam/</guid>
      <description>&lt;p&gt;I fell in love with these painting by John Gannam, an American artist from the mid-1900s.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;A beautiful painting of a woman about to wake up a man by pouring water on him&#34;
    width=&#34;640&#34;
    height=&#34;570&#34;
    src=&#34;https://nathanherald.com/posts/john-gannam/1_hu2710022540497387774.jpg&#34;&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;A beautiful painting of a woman leaning against a tree on a sunny day&#34;
    width=&#34;435&#34;
    height=&#34;615&#34;
    src=&#34;https://nathanherald.com/posts/john-gannam/2_hu17767847408544453696.jpg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;Checkout more of &lt;a href=&#34;https://www.artnet.com/artists/john-gannam/&#34;&gt;their work on artnet&lt;/a&gt; and read more about &lt;a href=&#34;https://societyillustrators.org/award-winners/john-gannam/&#34;&gt;them on Society of Illustrators&lt;/a&gt;.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Cassette mixing 🤯</title>
      <link>https://www.youtube.com/watch?v=trlYlJU4YQc</link>
      <pubDate>Tue, 28 Feb 2023 20:40:22 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/cassette-mixing/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/trlYlJU4YQc&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;Via &lt;a href=&#34;https://twitter.com/musicthing/status/1348757744510259203&#34;&gt;@musicthing&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=trlYlJU4YQc&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Dublin Goth New Wave Movement, 1989</title>
      <link>https://www.youtube.com/watch?v=Uv6Sxh1hi7w</link>
      <pubDate>Mon, 27 Feb 2023 21:50:12 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/dublin-goth-new-wave-movement-1989/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/Uv6Sxh1hi7w&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;Via &lt;a href=&#34;http://www.thingsmagazine.net/blurry-sounds/&#34;&gt;things magazine&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=Uv6Sxh1hi7w&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>The Circle (Shortfilm)</title>
      <link>https://vimeo.com/709514622</link>
      <pubDate>Sun, 26 Feb 2023 22:52:46 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/the-circle/</guid>
      <description>
&lt;iframe src=&#34;https://player.vimeo.com/video/709514622?h=d3f1da99a9&amp;amp;dnt=1&amp;amp;app_id=122963&#34; width=&#34;240&#34; height=&#34;240&#34; frameborder=&#34;0&#34; allow=&#34;autoplay; fullscreen; picture-in-picture&#34; allowfullscreen title=&#34;The Circle (Shortfilm)&#34;&gt;&lt;/iframe&gt;


&lt;p&gt;Via &lt;a href=&#34;https://www.swiss-miss.com/2022/05/the-circle-shortfilm.html&#34;&gt;Swissmiss&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://vimeo.com/709514622&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>TIL about the useUnknownInCatchVariables compiler flag for TypeScript</title>
      <link>https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-4.html#defaulting-to-the-unknown-type-in-catch-variables---useunknownincatchvariables</link>
      <pubDate>Sat, 11 Feb 2023 00:08:28 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/use-unknown-in-catch-variables/</guid>
      <description>&lt;p&gt;Today I was debugging a confusing situation.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The variable inside a &lt;code&gt;catch&lt;/code&gt; statement in TypeScript is &lt;code&gt;any&lt;/code&gt; by default, which is awful&lt;/li&gt;
&lt;li&gt;Safari was throwing &lt;code&gt;undefined&lt;/code&gt; when trying to place a &lt;code&gt;webm&lt;/code&gt; file into a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element, which was breaking my brain 🤯&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I had mistakenly done this, assuming &lt;code&gt;e&lt;/code&gt; would be an &lt;code&gt;Error&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;putIntoVideoTag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;logger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;What&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;‽’&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So when &lt;code&gt;e&lt;/code&gt; was &lt;code&gt;undefined&lt;/code&gt;, it exploded.&lt;/p&gt;
&lt;p&gt;One can manually type the &lt;code&gt;catch&lt;/code&gt; variable so the compiler won’t let you accidentally call &lt;code&gt;e.name&lt;/code&gt;, like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;putIntoVideoTag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;unknown&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;logger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;What&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;‽’&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But that means I can (and will) forget to do it.&lt;/p&gt;
&lt;p&gt;Well, TIL that TypeScript now supports a flag to make all &lt;code&gt;catch&lt;/code&gt; variables default to &lt;code&gt;unknown&lt;/code&gt; which is great: &lt;code&gt;useUnknownInCatchVariables&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It was added in &lt;a href=&#34;https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-4.html#defaulting-to-the-unknown-type-in-catch-variables---useunknownincatchvariables&#34;&gt;the 4.4 release&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is now the default in all my TypeScript projects ✨&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-4.html#defaulting-to-the-unknown-type-in-catch-variables---useunknownincatchvariables&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Mid-day ☀️ 📸</title>
      <link>https://nathanherald.com/posts/mid-day-sunny-photo/</link>
      <pubDate>Tue, 07 Feb 2023 20:20:32 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/mid-day-sunny-photo/</guid>
      <description>&lt;p&gt;I took this earlier today and it really turned out fantastic. 😊&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;A photo of an entranceway, with mailboxes to the right, stairs to the left, and some bicycles leaning against the sides, with the sun beaming in with a little lens flare&#34;
    width=&#34;1024&#34;
    height=&#34;708&#34;
    src=&#34;https://nathanherald.com/posts/mid-day-sunny-photo/photo@2x_hu14794142915138661858.jpg&#34;&gt;
&lt;/figure&gt;


        
      </description>
    </item>
    <item>
      <title>Living Alone in the Wild Siberian Forest for 20 years in Yakutia</title>
      <link>https://www.youtube.com/watch?v=IOltGIaDPlY</link>
      <pubDate>Tue, 07 Feb 2023 19:51:53 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/living-alone-in-siberian-forest-for-20-years/</guid>
      <description>&lt;p&gt;I watched this the other day and I’m still thinking about it. While I’d sometimes love to live in a cabin in the woods, I’m not sure if this is how far I could take it. 😳&lt;/p&gt;
&lt;p&gt;It’s a lovely video, lovingly made, and worth a watch.&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/IOltGIaDPlY&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=IOltGIaDPlY&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Everyone thinks everyone is a scammer these days</title>
      <link>https://nathanherald.com/posts/scammer/</link>
      <pubDate>Tue, 07 Feb 2023 13:30:54 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/scammer/</guid>
      <description>&lt;p&gt;Or at least that’s how I feel right now. My personal site here doesn’t get a ton of updates, but I try to keep it alive by posting links to cool stuff I find on the internet from time-to-time.&lt;/p&gt;
&lt;p&gt;Well, today, I got a very hateful and angry email from a dude, because I posted a link to their website. I’m sure he’s a nice guy in real life, but this wasn’t the best email for me to get this morning.&lt;/p&gt;
&lt;p&gt;You can read about it on the page for the link post he is so angry about: &lt;a href=&#34;https://nathanherald.com/posts/links/modular-office-unit&#34;&gt;Modular Office Unit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;&lt;a href=&#34;https://github.com/myobie/nathanherald.com/commit/e2ed2d216f663d895c4967beb05859b1958b58fa&#34;&gt;I took the link down&lt;/a&gt;, because I don’t want anyone to be upset because of a dumb link I posted. It’s just a link.&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;The email really shocked me, and so I took the link down because I had some other stuff I needed to get to. But after taking some time to think about it, I decided to put the link back, add a big disclaimer, and just describe what happened. You can &lt;a href=&#34;https://nathanherald.com/posts/links/modular-office-unit&#34;&gt;read the post&lt;/a&gt; and determine for yourself if I am claiming to either build furniture or write long-form prose about furniture on some other domain…&lt;/p&gt;
&lt;p&gt;Is my site confusing? Does he think I’m claiming I made &lt;a href=&#34;https://nathanherald.com/writing/links/takako-mamiya-love-trip/&#34;&gt;a Japanese pop album in 1982&lt;/a&gt;, before I was born? Is it unclear that I am a real person, and I just post links to things I think are cool?&lt;/p&gt;
&lt;p&gt;I guess not everyone knows that “links” means links to other places, other things that are not related to me, so maybe I should go and add “to other places” or “I promise I am not claiming I made this, it’s just a link” to every link?&lt;/p&gt;
&lt;p&gt;In the moment, I feel more wary of posting any more links at all here. I’m sure I’ll get over it by the end of today. Maybe I should post more often so it’s clear I am a real person…&lt;/p&gt;
&lt;p&gt;Super weird email to get tho, for sure… I gotta shake it off and get back to work.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update: I made some small changes to my site to make super extra clear that links to sites that are not “my name dot com” are to things that I don’t own or have anything to do with. I thought that was obvious before, and now hopefully it’s super obvious. Sheesh.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I updated this article on 8 Feb, 2023.&lt;/em&gt;&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Love Trip – Takako Mamiya</title>
      <link>https://www.youtube.com/watch?v=ovcnIPL-CWs</link>
      <pubDate>Thu, 12 Jan 2023 21:05:05 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/takako-mamiya-love-trip/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/ovcnIPL-CWs&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=ovcnIPL-CWs&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Had to update Whitney – the font here</title>
      <link>https://nathanherald.com/posts/updated-the-font/</link>
      <pubDate>Sun, 08 Jan 2023 11:28:25 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/updated-the-font/</guid>
      <description>&lt;p&gt;I use &lt;a href=&#34;https://www.typography.com/webfonts&#34;&gt;typography.com’s cloud service&lt;/a&gt; so I can use the wonderful &lt;a href=&#34;https://www.typography.com/fonts/whitney/design-notes&#34;&gt;Whitney&lt;/a&gt; here for my personal website 🥰&lt;/p&gt;
&lt;p&gt;However, with &lt;a href=&#34;https://nathanherald.com/writing/links/ana-vidovi%C4%87-classical-guitar/&#34;&gt;the last post I made&lt;/a&gt;, the artist’s name had a character in it which didn’t show up correctly: ć. This bothered me quite a bit, so I dug into the “Cloud Dashboard” to see what might be the issue, and it turns out the settings (which I assume are the defaults) are to not provide every possible character as part of the font package – I assume to save some bytes.&lt;/p&gt;
&lt;p&gt;I changed from “Basic Latin Accents” to “Extended Latin Accents” and now the ć shows up.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the typography.com dashboard, showing the change from Basic Latin Accents to Extended Latin Accents&#34;
    width=&#34;784&#34;
    height=&#34;666&#34;
    src=&#34;https://nathanherald.com/posts/updated-the-font/screenshot@2x_hu9135829418333451344.png&#34;&gt;
&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;Side note: I am not sure if this “Cloud.typography” service is really being maintained. The dashboard UI has never changed, and appears really tiny on any modern computer screen. They don’t really link to the service from their navigation at all anymore, I had to google the service to find the url to its page. They do link to it during checkout, but it makes we wonder if maybe it’s being deemphasized.&lt;/p&gt;
&lt;p&gt;Either way, this is the only way I can use Whitney here, so it’s worth the trouble I guess. 😞 Maybe I should buy the &lt;a href=&#34;https://www.typography.com/fonts/digital-type-capsule/overview&#34;&gt;Digital Type Capsule&lt;/a&gt;, download them for offline use as woff files, and use those instead?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What’s the lesson? Just use the entire font file, don’t try to be cute and slim it down. You never know when you might want to use a character which the font supports, but with your version doesn’t include.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Ana Vidović playing classical guitar</title>
      <link>https://www.youtube.com/watch?v=e26zZ83Oh6Y</link>
      <pubDate>Sun, 08 Jan 2023 11:05:00 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/ana-vidovi%C4%87-classical-guitar/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/e26zZ83Oh6Y&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=e26zZ83Oh6Y&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Robohands - Green</title>
      <link>https://robohands.bandcamp.com/album/robohands-green</link>
      <pubDate>Sat, 07 Jan 2023 18:53:36 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/robohands-green/</guid>
      <description>&lt;p&gt;I’ve been listeneing to this super good ambient jazz album on repeat while working. You can get it &lt;a href=&#34;https://robohands.bandcamp.com/album/robohands-green&#34;&gt;on Bandcamp&lt;/a&gt; or on Apple Music:&lt;/p&gt;

&lt;iframe allow=&#34;autoplay *; encrypted-media *;&#34; frameborder=&#34;0&#34; width=&#34;660&#34; height=&#34;450&#34; style=&#34;overflow:hidden;background:transparent;&#34; sandbox=&#34;allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation&#34; src=&#34;https://embed.music.apple.com/us/album/green/1623410474&#34;&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://robohands.bandcamp.com/album/robohands-green&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Casiopea - Mint Jams</title>
      <link>https://music.apple.com/us/album/mint-jams-live/1535034571</link>
      <pubDate>Fri, 06 Jan 2023 11:50:04 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/casiopea-mint-jams/</guid>
      <description>&lt;p&gt;I have been super into this album (Apple Music link). Super great music for when I’m working this week. I found it on YouTube, which has been recommending me music like this recently for some reason 🤔&lt;/p&gt;

&lt;iframe allow=&#34;autoplay *; encrypted-media *;&#34; frameborder=&#34;0&#34; width=&#34;660&#34; height=&#34;450&#34; style=&#34;overflow:hidden;background:transparent;&#34; sandbox=&#34;allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation&#34; src=&#34;https://embed.music.apple.com/us/album/mint-jams-live/1535034571&#34;&gt;&lt;/iframe&gt;


&lt;p&gt;The full album is also available on YouTube, if you don’t have/want Apple Music:&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/6GEI3PpXEAo&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://music.apple.com/us/album/mint-jams-live/1535034571&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Hydraulic Press Interpretive Dance</title>
      <link>https://www.youtube.com/channel/UCZuDcDy5AXUx3BO80woqWZA</link>
      <pubDate>Thu, 05 Jan 2023 09:45:11 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/hydraulic-press-interpretive-dance/</guid>
      <description>&lt;p&gt;I love these videos where &lt;a href=&#34;https://smacmccreanor.com&#34;&gt;Smac McCreanor&lt;/a&gt; imitates the viral hydraulic press videos with dance. The one embedded here is the entire series in one video:&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/zuIYjTc6lJE&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;&lt;em&gt;Via &lt;a href=&#34;https://kottke.org/23/01/hydraulic-press-interpretive-dances&#34;&gt;Kottke&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/channel/UCZuDcDy5AXUx3BO80woqWZA&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Modular Office Unit</title>
      <link>https://guglielmopoletti.com/modular-office-unit/</link>
      <pubDate>Wed, 04 Jan 2023 13:56:35 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/modular-office-unit/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Update about this link post:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I originally found a link about this modular office unit thing via &lt;a href=&#34;https://www.swiss-miss.com/2022/12/gasp-this-modular-space.html&#34;&gt;swiss-miss.com&lt;/a&gt;, I thought it was super cool, and so I posted the same link here from my site.&lt;/p&gt;
&lt;p&gt;Well, a dude at gessato.com sent me a very ugly email, asking me to take this link down. It seems he thinks I am taking credit for building the office thing, or writing the article about it, or something to that effect, as “a scam.”&lt;/p&gt;
&lt;p&gt;Here is what he wrote me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hi Scammer,&lt;br&gt;
why are you so full of shit of claiming you wrote this piece -  &lt;code&gt;https://www.gessato.com/modular-office-unit/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Take that link down immediately fucking scumbag.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’s the entire email. It shocked me and &lt;a href=&#34;https://nathanherald.com/posts/scammer/&#34;&gt;I wrote about it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I tried to explain that this is my personal website and it is obvious I did not make &lt;a href=&#34;https://nathanherald.com/posts/links/little-sims-no-thank-you/&#34;&gt;the music video by Little Sims&lt;/a&gt;, or record &lt;a href=&#34;https://nathanherald.com/posts/links/takako-mamiya-love-trip/&#34;&gt;the Japanese Pop Album from 1982&lt;/a&gt;, or make &lt;a href=&#34;https://nathanherald.com/posts/links/gods-country-trailer/&#34;&gt;the movie trailer for God’s country&lt;/a&gt; that I linked to, &lt;strong&gt;just like it’s clear I didn’t make this furniture or write anything for gessato.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Sadly, that didn’t make things better. He replied:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;WHY??&lt;br&gt;
because you are a liar. Because your web page  is very deceiving and it makes users believe that you actually WROTE that piece.&lt;br&gt;
We take pride in our content and do not tolerate people claiming ownership over it.&lt;br&gt;
Just remove the damn link and move on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, I guess just to be super extra clear to you the reader: &lt;strong&gt;I did not build this office unit, nor do I have anything to do with gessato.com&lt;/strong&gt; which should be obvious, but apparently is not.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A link from my site does not mean that I endorse the other site, that I work for or created the other site, or that I even know anything at all about the other site. I post blog posts and link posts on “my name dot com”, and so any content you see on “any other name dot com” is obviously unrelated to me. You can even tell which posts here are links by the link emoji (🔗) which has been next to them for years.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The modular office unit &lt;em&gt;was actually&lt;/em&gt; created by &lt;a href=&#34;https://guglielmopoletti.com/modular-office-unit/&#34;&gt;Guglielmo Poletti&lt;/a&gt; (who is obviously not me). I found it linked to by &lt;a href=&#34;https://www.swiss-miss.com/2022/12/gasp-this-modular-space.html&#34;&gt;swiss-miss.com&lt;/a&gt; which is obvisouly not my site (because it’s not “my name dot com”). I sure hope it’s clear that I don’t make furniture, nor write long articles about furniture.&lt;/p&gt;
&lt;p&gt;You can read below the short sentence I originally wrote about the office unit (which is definitely cool looking), and judge for yourself if I’m taking credit for something I shouldn’t.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I absolutely love this modular office unit! It includes a work area, tons of storage, a sleeping area, and even an elevated sitting area.&lt;/p&gt;

&lt;figure&gt;
&lt;img src=&#34;unit.jpg&#34; width=&#34;600&#34;&gt;
&lt;/figure&gt;


&lt;p&gt;And then, I originally had linked this post to &lt;code&gt;www.gessato.com/modular-office-unit&lt;/code&gt; so you could go there and see more about this office thingie. I do regret linking to them now, for sure.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;I updated this on 8 Feb 2023.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I changed the primary link to the website of Guglielmo Poletti, since he actually made the object in question, he has photos of if there if you want to see them, and I don’t think he’ll send me any angry emails about my link to his site.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I also originally linked to a desk, which was unrelated, but I’m just going to let this now only be about the modular office unit thing since it is such a big deal apparently.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;And finally, I also updated my site’s design to not only put a link emoji (🔗) next to link posts, but now put a star emoji (⭐️) next to blog posts. Maybe that will make it even more clear what a link is and what a blog post is. Sheesh.&lt;/em&gt;&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://guglielmopoletti.com/modular-office-unit/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>TIL about IndieKit</title>
      <link>https://getindiekit.com/</link>
      <pubDate>Wed, 04 Jan 2023 10:01:01 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/indie-kit/</guid>
      <description>&lt;p&gt;TIL about IndieKit, which is a small server to allow one to quickly post to a statically generated website (like mine), syndicate posts to other sites like Twitter or Mastodon (like I want to do), and seems pretty customizable.&lt;/p&gt;
&lt;p&gt;I’ve had things setup on my phone for a while where I can write a markdown post using &lt;a href=&#34;https://ia.net/writer&#34;&gt;iA Writer&lt;/a&gt;, committing it using &lt;a href=&#34;https://workingcopyapp.com&#34;&gt;Working Copy&lt;/a&gt;, and then it gets deployed with &lt;a href=&#34;https://www.netlify.com&#34;&gt;Netlify&lt;/a&gt;… but it’s all too much and too brittle.&lt;/p&gt;
&lt;p&gt;I really want a simple box I can type some text into, click “publish,” and then it appears here on my site. I don’t post as often as I’d like because of how difficult it is for me to post from my phone.&lt;/p&gt;
&lt;p&gt;I’m not sure if IndieKit is the answer for me (I really want a solution that uses Cloudflare Workers if possible), but I’m going to set something like this up soon… I hope 🤞&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://getindiekit.com/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>NO THANK YOU – Little Sims</title>
      <link>https://youtu.be/GqwOCq0MXPU</link>
      <pubDate>Sat, 17 Dec 2022 18:18:09 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/little-sims-no-thank-you/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/GqwOCq0MXPU&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://youtu.be/GqwOCq0MXPU&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>God’s Country Trailer</title>
      <link>https://youtu.be/92kQD4fc0a0</link>
      <pubDate>Thu, 11 Aug 2022 11:04:43 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/gods-country-trailer/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/92kQD4fc0a0&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://youtu.be/92kQD4fc0a0&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>New ❤️ album: Invisible Cities by A Winged Victory For The Sullen</title>
      <link>https://music.apple.com/us/album/invisible-cities/1538188398</link>
      <pubDate>Mon, 09 May 2022 11:02:58 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/a-winged-victory-for-the-sullen/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Album art for Invisible Cities by A Winged Victory For The Sullen&#34;
    width=&#34;600&#34;
    height=&#34;600&#34;
    src=&#34;https://nathanherald.com/posts/links/a-winged-victory-for-the-sullen/album-art@2x_hu2887348344071495623.jpg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;I’ve been a huge fan of A Winged Victory For The Sullen since their first album back in 2011. I’ve listened to that &lt;a href=&#34;https://music.apple.com/us/album/a-winged-victory-for-the-sullen/443010643&#34;&gt;self titled debut album&lt;/a&gt; so many times, it’s always at the top of any of my favorites or recap playlists on Apple Music 😅&lt;/p&gt;
&lt;p&gt;I also love to listen to anything by Duston O’Halloran (half of the group), especially the &lt;a href=&#34;https://music.apple.com/us/album/breathe-in-original-motion-picture-soundtrack/1500816588&#34;&gt;soundtrack to Breathe In&lt;/a&gt;. That is also an album I’ve listened to way to frequently, and yet never enough.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://music.apple.com/us/album/invisible-cities/1538188398&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Some technologies last</title>
      <link>https://nathanherald.com/posts/links/some-technologies-last/</link>
      <pubDate>Wed, 26 Jan 2022 08:33:00 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/some-technologies-last/</guid>
      <description>&lt;p&gt;When reading &lt;a href=&#34;https://adactio.com/journal/18743&#34;&gt;Jeremy Keith’s latest post&lt;/a&gt;, I was reminded that a lot of people seem to expect &lt;em&gt;all&lt;/em&gt; technologies to be short lived and frequently replaced – when this is not really true at all. Sure, tons of things come and go like phones, apps, and websites, but &lt;a href=&#34;https://en.wikipedia.org/wiki/HTML&#34;&gt;HTML&lt;/a&gt; has been with us for 29 years and will for sure be with us for the next 30 and more. &lt;em&gt;(There are tons of other things too, like &lt;a href=&#34;https://en.wikipedia.org/wiki/C_(programming_language)&#34;&gt;C&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/ASCII&#34;&gt;ASCII&lt;/a&gt;, which will probably also be with us forever…for better or worse.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a&#34;&gt;anchor element&lt;/a&gt; (or “link” as we usually call them) is one of the most important inventions ever because it is what creates “the worldwide web.” The &lt;a href=&#34;https://en.wikipedia.org/wiki/Internet&#34;&gt;internet&lt;/a&gt; is the plumbing so computers and devices can all be connected with wires (or wireless) and find each other, which is great, but the real value is “the web” of links we use to find sites and apps to talk to each other, purchase things we need, and generally live our lives. Even in some sort of “VR ‘meta’ 3D future” there will also be a need to link from one place to another, &lt;a href=&#34;https://en.wikipedia.org/wiki/Hyperlink&#34;&gt;“hyperlinks.”&lt;/a&gt;&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>TIL about explain (analyze, buffers) in postgresql</title>
      <link>https://www.pgmustard.com/docs/explain/buffers</link>
      <pubDate>Sun, 23 Jan 2022 18:55:28 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/postgres-buffers/</guid>
      <description>&lt;p&gt;While reading &lt;a href=&#34;https://pawelurbanek.com/postgresql-query-bottleneck&#34;&gt;some postgres tips&lt;/a&gt; today, as one does, I noticed a parameter to &lt;code&gt;explain&lt;/code&gt; I hadn&amp;rsquo;t seen before:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;EXPLAIN&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;ANALYZE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;BUFFERS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;users&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;WHERE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;lower&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;email@example.com&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;buffers&lt;/code&gt; would have been super useful recently when I was debugging some queries! And there are more I didn&amp;rsquo;t know about like &lt;code&gt;costs off&lt;/code&gt;, &lt;code&gt;timing off&lt;/code&gt;, and &lt;code&gt;summary off&lt;/code&gt;. Next time I&amp;rsquo;ll be more prepared.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.pgmustard.com/docs/explain/buffers&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>3 out of 3 💉💉💉</title>
      <link>https://nathanherald.com/posts/3-out-of-3/</link>
      <pubDate>Wed, 12 Jan 2022 14:20:18 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/3-out-of-3/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve gotten my third shot and, like the other two, I had a pretty high fever for about 24 hours. For three out of three shots I&amp;rsquo;ve had the same side-effects: high fever and arm pain. My expectation is we will get a shot every year and that I will have the same side-effects each time 😅 I don&amp;rsquo;t mind, it&amp;rsquo;s very much worth it, and it&amp;rsquo;s not hard to just wait when it&amp;rsquo;s expected and extremely likely to subside soon. I&amp;rsquo;m grateful to have access to free vaccines and to have pretty light side-effects after 💪&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>TIL about Gaia</title>
      <link>https://en.wikipedia.org/wiki/Gaia_%28spacecraft%29</link>
      <pubDate>Fri, 07 Jan 2022 22:30:57 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/gaia/</guid>
      <description>&lt;p&gt;Gaia is a space observatory launched by the &lt;a href=&#34;https://en.wikipedia.org/wiki/European_Space_Agency&#34;&gt;ESA&lt;/a&gt; on 19 Dec 2013 to map the positions and velocities of stars in our and nearby  galaxies! It orbits the sun at &lt;a href=&#34;https://en.wikipedia.org/wiki/Lagrangian_point#L2&#34;&gt;L2&lt;/a&gt;, just like &lt;a href=&#34;https://nathanherald.com/writing/links/james-web-space-telescope/&#34;&gt;Webby Scopes&lt;/a&gt; will.&lt;/p&gt;
&lt;p&gt;It’s amazing to see the star charts produced and reminds me of the navigational charts used in many SciFi films. See this example:&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/bzQUNCleS3o&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://en.wikipedia.org/wiki/Gaia_%28spacecraft%29&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Where Is Webb?</title>
      <link>https://webb.nasa.gov/content/webbLaunch/whereIsWebb.html</link>
      <pubDate>Mon, 03 Jan 2022 11:05:50 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/where-is-webb/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the Where Is Webb site&#34;
    width=&#34;681&#34;
    height=&#34;1200&#34;
    src=&#34;https://nathanherald.com/posts/links/where-is-webb/webbsite_hu1989581768415926751.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;It’s amazing we can see the speed and position of the telescope in real-time. Also, temperate data recently started streaming down 💪&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://webb.nasa.gov/content/webbLaunch/whereIsWebb.html&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>The Dark Forest</title>
      <link>https://youtube.com/watch?v=xAUJYP8tnRE&amp;feature=share</link>
      <pubDate>Sun, 02 Jan 2022 11:54:19 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/the-dark-forest/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/xAUJYP8tnRE&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;This animation style is so nice 😊 Also, I never considered the idea that it could be better to stay quiet and not broadcast out our existence 😬&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://youtube.com/watch?v=xAUJYP8tnRE&amp;feature=share&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>The James Webb Space Telescope</title>
      <link>https://www.youtube.com/watch?v=lrY04VPDg8I</link>
      <pubDate>Sat, 01 Jan 2022 11:33:19 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/james-web-space-telescope/</guid>
      <description>&lt;p&gt;It’s been a very exciting week! In case you aren’t aware, we (humans) have sent a new telescope out into space. Unlike &lt;a href=&#34;https://en.wikipedia.org/wiki/Hubble_Space_Telescope&#34;&gt;Hubble&lt;/a&gt;, it’s tuned to see infra-red with the goal of collecting the red-shifted light just getting to us from the earliest galaxies and stars.&lt;/p&gt;
&lt;p&gt;This one won’t orbit the Earth, instead it will orbit the Sun along with the Earth. It will sit at a spot called the &lt;a href=&#34;https://en.wikipedia.org/wiki/Lagrange_point&#34;&gt;“L2”&lt;/a&gt;, where the gravitational effect of both the Sun and Earth are equal on the satellite. This is the first object we’ve ever tried to install into a Lagrange point! &lt;em&gt;Also, it amazes me that someone discovered and calculated these points in 1772 👀&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It’s a pretty stressful situation. First, the launch had to go well (which it did). Then, the separation has to go well (which it did). Now, as it travels a million miles to the L2, it will slowly start to expand and unfold. If anything goes wrong, then they’ll try to rotate and shake it and things remotely, but it’s not an option to send anyone over to it for repairs 😬&lt;/p&gt;
&lt;p&gt;It’s so exciting to see a what we can do if we work together, if we put our money toward a goal, and if we stick with something even if it is difficult and takes a long time.&lt;/p&gt;
&lt;h3 id=&#34;videos&#34;&gt;Videos&lt;/h3&gt;
&lt;p&gt;Watch this awesomely cringe video that explains a lot and is just very enjoyable to watch:&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/lrY04VPDg8I&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;Or the best, short video I’ve found is this one:&lt;/p&gt;

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/Gx4uq83GSrE&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;h3 id=&#34;some-things-take-time&#34;&gt;Some things take time&lt;/h3&gt;
&lt;p&gt;I have this poster hanging in my home:&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Poster reading: Some things take time&#34;
    width=&#34;886&#34;
    height=&#34;1200&#34;
    src=&#34;https://nathanherald.com/posts/links/james-web-space-telescope/poster_hu1294017182858983506.jpg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;I’ve been thinking about it more and more as I learn more about the JWST and what it took to build and deploy it. It’s amazing and inspiring to me to see the effort everyone put into JWST succeed.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=lrY04VPDg8I&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Collaboration through software: digital paper and card catalogs</title>
      <link>https://nathanherald.com/posts/digital-card-catalog/</link>
      <pubDate>Tue, 04 May 2021 08:22:42 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/digital-card-catalog/</guid>
      <description>&lt;p&gt;Recently, as we work on completing our proof-of-concept version of &lt;a href=&#34;https://shareup.app&#34;&gt;Shareup&lt;/a&gt;, we’ve often been talking about different ways we can best help people work together through our software. I’ve never taken the time to write down how I think about collaborative software, so in this post I’ll attempt to. These concepts are how Adam, Anthony, and I are approaching building our new product and drive a lot of our conversations about how it will work.&lt;/p&gt;
&lt;p&gt;Over the past couple decades, while working on different distributed software and systems, I’ve been able to refine my thoughts about how to categorize and talk about collaborating through software. Also, I enjoy reading about software history and the many HCI research papers one can find about collaboration, like: &lt;a href=&#34;https://www.billbuxton.com/3sCompany.pdf&#34;&gt;AR video conferencing&lt;/a&gt;, &lt;a href=&#34;https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.589.7276&amp;amp;rep=rep1&amp;amp;type=pdf&#34;&gt;Telehealth&lt;/a&gt;, and &lt;a href=&#34;https://www.tandfonline.com/doi/pdf/10.1080/10447318.2019.1619259&#34;&gt;related topics&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/PARC_(company)&#34;&gt;Xerox PARC&lt;/a&gt;, from way back when, are responsible for so many different technologies: &lt;a href=&#34;https://en.wikipedia.org/wiki/Ethernet&#34;&gt;Ethernet&lt;/a&gt;, &lt;a href=&#34;https://en.wikipedia.org/wiki/Videotelephony&#34;&gt;“video links”&lt;/a&gt; and &lt;a href=&#34;https://www.researchgate.net/publication/2688342_Personal_Distributed_Computing_The_Alto_and_Ethernet_Software&#34;&gt;distributed software&lt;/a&gt; (called “ethernet software” at the time) to help people work with &lt;a href=&#34;http://www.dlib.org/dlib/june96/hearst/06hearst.html&#34;&gt;“digital paper.”&lt;/a&gt; This concept of &lt;strong&gt;digital paper&lt;/strong&gt; really resonates with me most in my experience building software and has impacted how I think about designing a space for collaboration.&lt;/p&gt;
&lt;h2 id=&#34;digital-paper&#34;&gt;Digital paper&lt;/h2&gt;
&lt;p&gt;I like to always start thinking about a piece of software or a system as a group of people sitting around a table with a sheet of paper and some markers. Everyone can clearly see each other, can easily see when someone is writing or drawing, and it’s not possible to make a mark that cannot be seen by everyone else. Also, everything is spatially arranged: when I make a mark to the left of yours, you also see that it’s to the left of the mark you previously made.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://miro.com&#34;&gt;Miro&lt;/a&gt;, &lt;a href=&#34;https://www.figma.com&#34;&gt;Figma&lt;/a&gt; (and now &lt;a href=&#34;https://www.figma.com/figjam/&#34;&gt;FigJam&lt;/a&gt;), &lt;a href=&#34;https://www.google.com/docs/about/&#34;&gt;Google Docs&lt;/a&gt;, and &lt;a href=&#34;https://www.craft.do&#34;&gt;Craft&lt;/a&gt; pages are examples of “digital paper.” &lt;em&gt;(I also sometimes call them “digital whiteboards.”)&lt;/em&gt; They work like a magic, digitally replicated piece of paper that everyone can see and write on together. Everything is spatially arranged, we can see each other’s “fingers” (cursors), and all changes are immediately visible to everyone.&lt;/p&gt;
&lt;p&gt;Digital whiteboards are the easiest collaborative software to talk about because they are both extremely immediate and give everyone 100% visibility. I cannot insert a circle into a Figma doc that you cannot see. The rules or “physics” of the software can be talked through very quickly and everyone “gets it.”&lt;/p&gt;
&lt;p&gt;I recently read &lt;a href=&#34;https://www.billbuxton.com/3sCompany.pdf&#34;&gt;a Microsoft Research paper titled &lt;em&gt;“Three&amp;rsquo;s Company: Understanding Communication Channels in Three-way Distributed Collaboration”&lt;/em&gt;&lt;/a&gt; where each participant could see “shadow images” of the other participants arms and hands as they would attempt to work together to accomplish a task like moving a block. Everyone also had audio and video feeds from the others. It seemed like everyone got it and the “additional arms” didn’t seem to bother anyone:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Despite the fact that we regularly saw three, and occasionally saw six arm shadows (two per person) in the task space, users did not seem to be distracted by this additional clutter.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;(I also found the feedback from participants that the video feed was only really useful when preparing for a task, but not during it, super interesting.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I also really liked &lt;a href=&#34;https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.589.7276&amp;amp;rep=rep1&amp;amp;type=pdf&#34;&gt;another paper by Bill Buxton titled &lt;em&gt;“Telepresence: Integrating Shared Task and Person Spaces”&lt;/em&gt;&lt;/a&gt; which has a great part which says (emphasis mine):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;… the interaction breaks out of being like watching TV, into a direct engagement of the participants. &lt;strong&gt;They meet each other, not the system.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;what-about-email&#34;&gt;What about email?&lt;/h2&gt;
&lt;p&gt;Email can also be considered a “digital paper” product, however its immutability and the slow cycle of feedback make it less useful for a lot of tasks. Now, people love email. It’s the most successful distributed technology ever created.&lt;/p&gt;
&lt;p&gt;The rules are understandable: I write something in a place that only I can see, then I hit “send” which will transfer a copy over to another person’s inbox (where I cannot see) for them to read sometime in the future. I can’t “unsend” an email and I can’t modify an email after it’s been sent.&lt;/p&gt;
&lt;p&gt;For tasks which don’t need immediacy or really resemble letter writing, then email is the right choice. Personally, email doesn’t interest me much as something to work on. I wish the email protocols could be updated to allow for easy end-to-end encryption, auto-rejection of unknown senders, etc to make it more private and less overwhelming – but I also really get why it’s hard to change something so old and so successful.&lt;/p&gt;
&lt;h2 id=&#34;what-about-chat&#34;&gt;What about chat?&lt;/h2&gt;
&lt;p&gt;Chat software can be a little fuzzy to pin down. While it might seem like a Slack channel is both fully immediate and fully visible, it turns out there are things one can do that are not shared and Slack tries to make this clear by labelling those messages “Only visible to you”.&lt;/p&gt;

&lt;figure&gt;
&lt;img src=&#34;slack-screenshot.png&#34; width=&#34;468&#34; height=&#34;148&#34;&gt;
&lt;/figure&gt;


&lt;p&gt;By making it less clear if everything is fully shared, &lt;strong&gt;it adds stress&lt;/strong&gt; for users who feel uneasy about trying something because “what if everyone sees me make a mistake” or “what if I send this to the wrong place.” &lt;em&gt;(The old &lt;a href=&#34;https://en.wikipedia.org/wiki/Internet_Relay_Chat&#34;&gt;IRC&lt;/a&gt; software also had ways for “the system” to message you that were only visible to you, so this is not a problem unique to Slack.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Using Apple’s Messages can also be confusing: if I delete a message, then does it delete it for the other person? &lt;em&gt;(No, it doesn’t.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I prefer software that is immediate: I would almost always rather use chat over email. But I also prefer software that is visible: I don’t like to be confused about who can see what.&lt;/p&gt;
&lt;h2 id=&#34;digital-paper-isnt-enough&#34;&gt;Digital paper isn’t enough&lt;/h2&gt;
&lt;p&gt;Digital paper (or a digital whiteboard) is not enough.  Arranging everything spatially can make it very difficult to find or remember where something was placed. As a canvas for a document grows it becomes harder and harder to keep up. Changes can happen over in an area one is not currently paying attention to. And searching for a note or comment from a month ago is almost impossible.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Both Miro and Figma continue to add features to try to help mitigate some of these issues (like &lt;a href=&#34;https://help.figma.com/hc/en-us/articles/360040322673-Follow-Along-with-Observation-Mode&#34;&gt;following a person’s viewpoint&lt;/a&gt;) which is great. Google Docs can have the table of contents to the left which can help some as well.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The metaphor I use to talk about solving this issue is a digital card catalog.&lt;/p&gt;
&lt;h2 id=&#34;digital-card-catalog&#34;&gt;Digital card catalog&lt;/h2&gt;
&lt;p&gt;When I design collaborative software, the metaphor I use the most is a digital card deck or digital card catalog, depending on the size and scope of the software.&lt;/p&gt;
&lt;p&gt;If we each have a &lt;a href=&#34;https://deck.of.cards&#34;&gt;deck of playing cards&lt;/a&gt;, and we shuffle them in different ways, we know we still have the same “set of cards” despite the orders being different. I might have the ace of diamonds as my first card and you might have the queen of hearts, but we are not confused by that in any way. I can always search through and find any card you can. Also, if I decide to filter through and make a pile of only hearts, we understand that I am not filtering any cards in your deck.&lt;/p&gt;
&lt;p&gt;For a “digital deck of cards” system: the card is like a little shared document. When we draw on the shared ace of clubs, we can all see the updated design immediately. I can also choose to put that card away in the deck and start working on another card. Then I won’t see your updates immediately because I’m on a different piece of digital paper. Everything inside the card is shared and everything outside is personal.&lt;/p&gt;
&lt;p&gt;Sometimes we need a shared way to organize lots of cards which is what I call the “digital card catalog” system: not only is the card a shared digital document, the way the cards are organized is also shared. This is analogous to the &lt;a href=&#34;https://www.infobloom.com/what-is-a-card-catalog.htm&#34;&gt;card catalogs found in libraries&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Aside: a long time ago I learned from a few designers that if one wants inspiration for how to design information or software for people then always look at maps and libraries. They are ways to structure and organize lots of information, they are designed to be heavily used, and they try to work for a large number of people with as little required training as possible. Also, &lt;a href=&#34;https://www.library.wisc.edu/gwslibrarian/bibliographies/disrupting-whiteness-in-libraries/&#34;&gt;be aware that there are systemic biases inside libraries and the libarary science field&lt;/a&gt; that are important to know about and not replicate.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While a “deck of cards” is a nice and easy analogy, almost all types of software that I have helped design or helped build have become larger “card catalog” systems.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Wunderlist&#34;&gt;Wunderlist&lt;/a&gt; was one: each task was like a little shared document. Any change you make to task is visible to every member of the shared list. And the list itself is the “catalog.” We all see the same tasks in the list at all times.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One contentious argument we had was whether sorting a list alphabetically was a “shared action” or not. IMO, using the physical analogy to card catalogs, sorting or filtering is not a shared action. However, I lost this argument, and when someone sorted a list it actually sorted those tasks for everyone. I still feel this was a mistake and wish I could have done a better job making the case for personal-only sorting.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;People prefer &lt;a href=&#34;https://uxdesign.cc/design-principle-consistency-6b0cf7e7339f&#34;&gt;consistent rules&lt;/a&gt; or “physics.” When we are crafting the “universe” of a software product it’s important the physics and “gravity” are consistent and feel good. The rules of physical card catalog systems are already well documented and understood, so one can leverage that to help make our digital card catalog systems easier to use.&lt;/p&gt;
&lt;p&gt;One can also look to libraries for how to “announce updates” since it is really impossible for a human to keep track of all the “new cards” in a busy catalog. There are “new releases” or “librarian picks” which try to help guide a person to relevant topics and places. There are many methods to search, filter, and arrange the cards to make it easier to find a specific one.&lt;/p&gt;
&lt;p&gt;One could make “topic drawers” which include only the cards relevant to that topic. There is no reason why the same card cannot be present in more than one drawer if it were relevant to many topics. A card could helpfully indicate which other topic drawers it could be found in. This would make sense and be welcomed by library visitors.&lt;/p&gt;
&lt;p&gt;Sometimes, in physical libraries, there are cards in the catalog which reference books over at other libraries: a &lt;a href=&#34;https://en.wikipedia.org/wiki/Union_catalog&#34;&gt;union card catalog.&lt;/a&gt; Digitally, it’s possible to have a system to not just work with information inside your group or organization, but also with other groups and organizations which could be a huge win. 🙌&lt;/p&gt;
&lt;h2 id=&#34;quick-summary&#34;&gt;Quick summary&lt;/h2&gt;
&lt;p&gt;So that’s what I wanted to write about: when I design or build distributed software to help people work together, I almost always think about &lt;em&gt;digital paper&lt;/em&gt; organized in a &lt;em&gt;digital card catalog.&lt;/em&gt; I prefer software to be &lt;em&gt;extremely immediate&lt;/em&gt; and it to be clear &lt;em&gt;what is shared?&lt;/em&gt; and what is not.&lt;/p&gt;
&lt;p&gt;I’m curious though, how do you think about collaborative distributed software? How do you think through the rules or &lt;em&gt;physics&lt;/em&gt; of the system? &lt;a href=&#34;mailto:myobie@hey.com&#34;&gt;Send me an email&lt;/a&gt;, I am very curious to hear what you think.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>TIL about less -S</title>
      <link>https://nathanherald.com/posts/til/less-dash-s/</link>
      <pubDate>Fri, 12 Feb 2021 12:55:10 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/less-dash-s/</guid>
      <description>&lt;p&gt;I was pretty frustrated by some tools that kept outputting way too much info in my terminal so I read through &lt;code&gt;man less&lt;/code&gt; and finally found the option to not wrap (&lt;code&gt;-S&lt;/code&gt; or &lt;code&gt;--chop-long-lines&lt;/code&gt;) and allow lines to scroll off the right edge. &lt;em&gt;One can move around with the left and right arrows to see all the info.&lt;/em&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of man less showing the documentation for the -S flag&#34;
    width=&#34;726&#34;
    height=&#34;426&#34;
    src=&#34;https://nathanherald.com/posts/til/less-dash-s/screenshot@2x_hu4725896710481944588.png&#34;&gt;
&lt;/figure&gt;


        
      </description>
    </item>
    <item>
      <title>Wonderful Portraits by Otis Kwame Kye Quaicoe</title>
      <link>https://www.thisiscolossal.com/2021/01/otis-kwame-kye-quaicoe-portraits/</link>
      <pubDate>Sun, 17 Jan 2021 23:03:15 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/otis-kwame-kye-quaicoe/</guid>
      <description>&lt;p&gt;These painting by Otis Kwame Kye Quaicoe really caught my eye on Colossal. I love the contrast and the wondrousness of the images. &lt;a href=&#34;https://instagram.com/otis_quaicoe&#34;&gt;Follow on Instagram&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Wiyaala&#34;
    width=&#34;943&#34;
    height=&#34;1200&#34;
    src=&#34;https://nathanherald.com/posts/links/otis-kwame-kye-quaicoe/1_hu1208424378532991892.jpg&#34;&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;Red Bandana on Green Suit&#34;
    width=&#34;899&#34;
    height=&#34;1200&#34;
    src=&#34;https://nathanherald.com/posts/links/otis-kwame-kye-quaicoe/2_hu5974254573337526160.jpg&#34;&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;Bandana Cowboy&#34;
    width=&#34;899&#34;
    height=&#34;1200&#34;
    src=&#34;https://nathanherald.com/posts/links/otis-kwame-kye-quaicoe/3_hu789502156185687339.jpg&#34;&gt;
&lt;/figure&gt;


        
          &lt;p&gt;
            &lt;a href=&#34;https://www.thisiscolossal.com/2021/01/otis-kwame-kye-quaicoe-portraits/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>TIL how to fix a formula conflict when updating brew</title>
      <link>https://nathanherald.com/posts/til/fix-brew-formula-conflict/</link>
      <pubDate>Thu, 31 Dec 2020 12:39:48 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/fix-brew-formula-conflict/</guid>
      <description>&lt;p&gt;Today, when updating brew, a formula I had made local modifications to had a conflict and had the &lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/code&gt; markers inside the ruby code, so it couldn&amp;rsquo;t function properly anymore. I was trying to find a quick way to just accept whatever the latest version of the formula is without my changes and this is what I came up with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ &lt;span class=&#34;nv&#34;&gt;NAME&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;whatever &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;dirname &lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;brew formula &lt;span class=&#34;nv&#34;&gt;$NAME&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; git checkout origin/master -- &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;NAME&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;.rb &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; -
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
        
      </description>
    </item>
    <item>
      <title>TIL about HTTP Client Hints</title>
      <link>https://tools.ietf.org/html/draft-ietf-httpbis-client-hints-06</link>
      <pubDate>Wed, 16 Dec 2020 12:30:08 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/client-hints/</guid>
      <description>&lt;p&gt;While reading &lt;a href=&#34;https://umaar.com/dev-tips/242-considerate-javascript/&#34;&gt;this article by Umar about considerate javascript&lt;/a&gt; I was surprised to see the &lt;code&gt;&amp;lt;meta http-equiv=&amp;quot;Accept-CH&amp;quot; …&amp;gt;&lt;/code&gt; method for appending request headers for client information! It&amp;rsquo;s called &lt;a href=&#34;https://tools.ietf.org/html/draft-ietf-httpbis-client-hints-06&#34;&gt;Client Hints&lt;/a&gt;. Sadly, it seems &lt;a href=&#34;https://caniuse.com/client-hints-dpr-width-viewport&#34;&gt;it&amp;rsquo;s Chrome-only&lt;/a&gt; 😔.&lt;/p&gt;
&lt;p&gt;This also lead me to the &lt;a href=&#34;https://wicg.github.io/netinfo/&#34;&gt;Network Information API spec&lt;/a&gt; which would be super useful, but &lt;a href=&#34;https://caniuse.com/netinfo&#34;&gt;is also Chrome-only&lt;/a&gt; 😔. The amount of Chrome-only APIs is ever growing and seems unstoppable, since all the browser makers rarely agree on priorities.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://tools.ietf.org/html/draft-ietf-httpbis-client-hints-06&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>TIL about ELIXIR_CLI_DRY_RUN for elixir and iex</title>
      <link>https://nathanherald.com/posts/til/elixir-dry-run/</link>
      <pubDate>Tue, 08 Dec 2020 14:28:38 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/elixir-dry-run/</guid>
      <description>&lt;p&gt;While I’ve been working on deploying some &lt;a href=&#34;https://hexdocs.pm/mix/Mix.Tasks.Release.html&#34;&gt;elixir releases&lt;/a&gt; I’ve been trying to understand how the flags I’m setting end up being seen by erlang. Well, it turns out elixir &lt;a href=&#34;https://github.com/elixir-lang/elixir/blob/76d245b6081c53228bf99fc1494add5de7872065/bin/elixir#L227&#34;&gt;has an ENV var for this&lt;/a&gt; named &lt;code&gt;ELIXIR_CLI_DRY_RUN&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is an example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ &lt;span class=&#34;nv&#34;&gt;ELIXIR_CLI_DRY_RUN&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; iex
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;erl -pa /usr/local/Cellar/elixir/1.11.2/bin/../lib/eex/ebin &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;		/usr/local/Cellar/elixir/1.11.2/bin/../lib/elixir/ebin &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;		/usr/local/Cellar/elixir/1.11.2/bin/../lib/ex_unit/ebin &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;		/usr/local/Cellar/elixir/1.11.2/bin/../lib/iex/ebin &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;		/usr/local/Cellar/elixir/1.11.2/bin/../lib/logger/ebin &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;		/usr/local/Cellar/elixir/1.11.2/bin/../lib/mix/ebin &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;	-noshell &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;	-user Elixir.IEx.CLI &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;	-extra --no-halt +iex
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can see a lot of interesting info here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;elixir&lt;/code&gt; or &lt;code&gt;iex&lt;/code&gt; are always just running &lt;code&gt;erl&lt;/code&gt; in the end&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-pa&lt;/code&gt; is to prepend a module load path and elixir makes sure all it’s modules are accessible at boot&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-noshell&lt;/code&gt; means to, uh, not start the erlang shell, since iex is going to boot up it’s own shell in it’s &lt;code&gt;start&lt;/code&gt; function with &lt;code&gt;:user_drv&lt;/code&gt; (&lt;a href=&#34;https://erlang.org/doc/man/erl.html&#34;&gt;find it in the erl docs&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-user&lt;/code&gt; is the first module erlang will call &lt;code&gt;start&lt;/code&gt; of after boot, in this instance it’s &lt;a href=&#34;https://github.com/elixir-lang/elixir/blob/76d245b6081c53228bf99fc1494add5de7872065/lib/iex/lib/iex/cli.ex#L52&#34;&gt;&lt;code&gt;Elixir.IEx.CLI&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-extra&lt;/code&gt; means “&lt;code&gt;erl&lt;/code&gt; won’t look at these, but they are available with &lt;a href=&#34;https://erlang.org/doc/man/init.html#get_plain_arguments-0&#34;&gt;&lt;code&gt;:init.get_plain_arguments()&lt;/code&gt;&lt;/a&gt; which &lt;a href=&#34;https://github.com/elixir-lang/elixir/search?q=get_plain_arguments&#34;&gt;elixir does a few times&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-halt&lt;/code&gt; tells elixir not to auto-exit after boot, so iex can setup it’s shell and accept input&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+iex&lt;/code&gt; is a special flag &lt;a href=&#34;https://github.com/elixir-lang/elixir/blob/76d245b6081c53228bf99fc1494add5de7872065/lib/elixir/lib/kernel/cli.ex#L301&#34;&gt;elixir looks for while starting up it’s CLI&lt;/a&gt; (which is also &lt;a href=&#34;https://github.com/elixir-lang/elixir/blob/76d245b6081c53228bf99fc1494add5de7872065/lib/iex/lib/iex/cli.ex#L127&#34;&gt;called by &lt;code&gt;IEx.start&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I found this super interesting and it helped me learn a lot about how erlang boots up, how modules are loaded, etc.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>TIL ⌃\ sends SIGQUIT to the foreground process</title>
      <link>https://nathanherald.com/posts/til/iex-control-backslash-2/</link>
      <pubDate>Thu, 05 Nov 2020 15:05:38 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/iex-control-backslash-2/</guid>
      <description>&lt;p&gt;Following up on my &lt;a href=&#34;https://nathanherald.com/writing/links/iex-control-backslash/&#34;&gt;previous TIL post about &lt;code&gt;iex&lt;/code&gt;&lt;/a&gt;, I learned today that I didn&amp;rsquo;t understand control characters in my shell at all. Running &lt;code&gt;stty -a&lt;/code&gt; shows my current shell settings:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ stty -a
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cchars: &lt;span class=&#34;nv&#34;&gt;discard&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^O&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;dsusp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^Y&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;eof&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^D&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;eol&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &amp;lt;undef&amp;gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nv&#34;&gt;eol2&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &amp;lt;undef&amp;gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;erase&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^?&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;intr&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^C&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;kill&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^U&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;lnext&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^V&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nv&#34;&gt;min&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; 1&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;quit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^&lt;span class=&#34;se&#34;&gt;\;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;reprint&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^R&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;start&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^Q&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;status&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^T&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nv&#34;&gt;stop&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^@&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;susp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^Z&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;time&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; 0&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;werase&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; ^W&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Control + backslash is right there, labelled as &amp;ldquo;quit&amp;rdquo; meaning it will send &lt;code&gt;SIGQUIT&lt;/code&gt; to the foreground process. Super handy.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>TIL one can exit iex with ⌃\</title>
      <link>https://hexdocs.pm/iex/IEx.html#module-exiting-the-shell</link>
      <pubDate>Sun, 01 Nov 2020 22:00:27 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/iex-control-backslash/</guid>
      <description>&lt;p&gt;I’ve been using ⌃C + ⌃C to exit &lt;code&gt;iex&lt;/code&gt; sessions for years. It turns out that works incidentally by invoking the break menu and then breaking out of that. This single keystroke method seems to be the most straightforward way to signal to the BEAM VM that it&amp;rsquo;s time to quit.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://hexdocs.pm/iex/IEx.html#module-exiting-the-shell&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Beogram 4000c • Recreated by Bang &amp; Olufsen</title>
      <link>https://www.bang-olufsen.com/en/speakers/beogram-4000c</link>
      <pubDate>Sun, 25 Oct 2020 21:15:45 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/beogram-4000c/</guid>
      <description>&lt;p&gt;😍&lt;/p&gt;

&lt;iframe allowFullScreen=&#34;allowFullScreen&#34; src=&#34;https://www.youtube.com/embed/RDZB_E39k2c?ecver=1&amp;amp;iv_load_policy=3&amp;amp;rel=0&amp;amp;yt:stretch=4:3&amp;amp;autohide=1&amp;amp;color=red&amp;amp;width=560&amp;amp;width=560&#34; width=&#34;560&#34; height=&#34;315&#34; allowtransparency=&#34;true&#34; frameborder=&#34;0&#34;&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://www.bang-olufsen.com/en/speakers/beogram-4000c&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>“I am excessively slothful, and wonderfully industrious—by fits.”</title>
      <link>https://nathanherald.com/posts/edgar-allen-poe-quote/</link>
      <pubDate>Thu, 22 Oct 2020 10:50:12 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/edgar-allen-poe-quote/</guid>
      <description>&lt;p&gt;A quote by Edgar Allen Poe from &lt;a href=&#34;https://www.eapoe.org/works/letters/p4407020.htm&#34;&gt;a letter in 1844&lt;/a&gt;.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Dieter Rams Wallpapers</title>
      <link>https://www.arun.is/dieter-rams-wallpapers/</link>
      <pubDate>Wed, 02 Sep 2020 10:22:03 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/dieter-rams-wallpapers/</guid>
      <description>&lt;p&gt;Awesome wallpapers for your phone or tablet feature one some of the most iconic product designs ever.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.arun.is/dieter-rams-wallpapers/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Connect, No Matter the Speed</title>
      <link>https://medium.com/google-design/connect-no-matter-the-speed-3b81cfd3355a</link>
      <pubDate>Wed, 02 Sep 2020 10:06:48 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/connect-no-matter/</guid>
      <description>&lt;p&gt;While the examples in this Medium article are all from google’s apps, this is still a valuable look at different experiences and concerns with limited or no connectivity.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://medium.com/google-design/connect-no-matter-the-speed-3b81cfd3355a&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>GitHub Profile README</title>
      <link>https://nathanherald.com/posts/github-profile-readme/</link>
      <pubDate>Mon, 31 Aug 2020 16:46:49 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/github-profile-readme/</guid>
      <description>&lt;p&gt;I’ve created a &lt;a href=&#34;https://docs.github.com/en/github/setting-up-and-managing-your-github-profile/managing-your-profile-readme&#34;&gt;GitHub profile README&lt;/a&gt; which is a special repository + file one can create which is then shown at the top of one’s profile page. You can see my new profile README at &lt;a href=&#34;https://github.com/myobie&#34;&gt;github.com/myobie&lt;/a&gt; and I’ve put a screenshot below.&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://github.com/myobie&#34;&gt;
  &lt;img
    alt=&#34;Screenshot of my github profile README&#34;
    width=&#34;686&#34;
    height=&#34;821&#34;
    src=&#34;https://nathanherald.com/posts/github-profile-readme/screenshot@2x_hu7545973017718503539.png&#34;&gt;
  &lt;/a&gt;
&lt;/figure&gt;


        
      </description>
    </item>
    <item>
      <title>TIL about hub-sync</title>
      <link>https://hub.github.com/hub-sync.1.html</link>
      <pubDate>Wed, 15 Jul 2020 10:55:35 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/til-hub-sync/</guid>
      <description>&lt;p&gt;TIL about &lt;code&gt;hub sync&lt;/code&gt; which will fetch &lt;code&gt;origin&lt;/code&gt;, merge into the local &lt;code&gt;main&lt;/code&gt; branch if possible, and delete any local branches that were deleted remotely 🙌&lt;/p&gt;
&lt;p&gt;If you don’t already use &lt;a href=&#34;https://github.com/github/hub&#34;&gt;&lt;code&gt;hub&lt;/code&gt;&lt;/a&gt; then I recommend you take a look ASAP. I’ve use &lt;code&gt;hub pull-request&lt;/code&gt; for some time, but never looked at all the commands it offers like &lt;code&gt;sync&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I &lt;code&gt;alias git=hub&lt;/code&gt; so I can type &lt;code&gt;git sync&lt;/code&gt; to catch everything up and I ❤️  it.&lt;/em&gt;&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://hub.github.com/hub-sync.1.html&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>29 automata in 6 minutes</title>
      <link>https://m.youtube.com/watch?feature=youtu.be&amp;v=-OJ1Yc2SwAs</link>
      <pubDate>Sat, 13 Jun 2020 10:56:28 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/29-automata/</guid>
      <description>&lt;p&gt;Wonderful.&lt;br&gt;
Found on &lt;a href=&#34;https://www.thisiscolossal.com/2020/06/federico-tobon-machines/&#34;&gt;Colossal&lt;/a&gt;.&lt;/p&gt;

&lt;iframe allowFullScreen=&#34;allowFullScreen&#34; src=&#34;https://www.youtube.com/embed/-OJ1Yc2SwAs?ecver=1&amp;amp;iv_load_policy=3&amp;amp;rel=0&amp;amp;yt:stretch=4:3&amp;amp;autohide=1&amp;amp;color=red&amp;amp;width=560&amp;amp;width=560&#34; width=&#34;560&#34; height=&#34;315&#34; allowtransparency=&#34;true&#34; frameborder=&#34;0&#34;&gt;&lt;/iframe&gt;



        
          &lt;p&gt;
            &lt;a href=&#34;https://m.youtube.com/watch?feature=youtu.be&amp;v=-OJ1Yc2SwAs&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>TIL about AbortController</title>
      <link>https://developer.mozilla.org/en-US/docs/Web/API/AbortController</link>
      <pubDate>Fri, 12 Jun 2020 14:58:16 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/abortcontroller/</guid>
      <description>&lt;p&gt;TIL about &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/AbortController&#34;&gt;AbortController&lt;/a&gt; which allows one to cancel a &lt;code&gt;fetch&lt;/code&gt;. HT to the Web Systems team at GitHub ❤️ &lt;a href=&#34;https://github.com/dgraham&#34;&gt;@dgraham&lt;/a&gt;, &lt;a href=&#34;https://github.com/keithamus&#34;&gt;@keithamus&lt;/a&gt;, &lt;a href=&#34;https://github.com/koddsson&#34;&gt;@koddsson&lt;/a&gt;, and &lt;a href=&#34;https://github.com/muan&#34;&gt;@muan&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/AbortController&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Your metrics might be lying to you – an example from Wunderlist</title>
      <link>https://nathanherald.com/posts/your-metrics-might-be-lying-to-you/</link>
      <pubDate>Wed, 10 Jun 2020 14:48:33 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/your-metrics-might-be-lying-to-you/</guid>
      <description>&lt;p&gt;We all love to see “increased engagement” from customers using the products we create, but it’s not always as clear as it might seem if the increase is positive or negative. It’s sometimes dangerous to have the singular goal to “increase usage of this feature” instead of “help customers get more done.” And even then, when our goal is 100% focused on helping the customer be a better human, we can sometimes misread the metrics we are collecting.&lt;/p&gt;

&lt;figure&gt;
&lt;img src=&#34;bell.svg&#34; width=&#34;600&#34; height=&#34;150&#34;&gt;
&lt;/figure&gt;


&lt;p&gt;At Wunderlist, the &lt;em&gt;notifications about recent activity&lt;/em&gt; feature was akin to the limit theorem: we iterated and iterated, but it always seemed like we couldn’t get to our ideal place to help customers see and understand what’s been happening in their lists. I believe we did our best, but there is one time where we thought we had improved things and it ended up we had done the opposite.&lt;/p&gt;
&lt;p&gt;We instrumented the apps to let us know when someone clicked on the &lt;em&gt;notification bell&lt;/em&gt; and we saw that hardly anyone was clicking on it. So we went to work: how could we help people know there is useful information in the &lt;em&gt;notifications popover&lt;/em&gt;.&lt;/p&gt;

&lt;figure&gt;
&lt;img src=&#34;bell-with-dot.svg&#34; width=&#34;600&#34; height=&#34;150&#34;&gt;
&lt;/figure&gt;


&lt;p&gt;We added a red dot to indicate “hey, there’s some new stuff here.” Clicking to show the &lt;em&gt;notification popover&lt;/em&gt; would remove the red dot &lt;em&gt;(until later when there is new activity)&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And our metrics for clicks on the &lt;em&gt;notification bell&lt;/em&gt; went way up! &lt;strong&gt;Success.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Luckily, we dug a bit deeper into the stats and we noticed a strange occurrence: often there were two clicks from the same device in rapid succession, both clicks in less than two seconds. We assumed we had made a mistake in our apps and they were reporting the click twice. But we couldn’t reproduce this ourselves and it wasn’t reported twice every time, just often.&lt;/p&gt;

&lt;figure&gt;
&lt;svg width=&#34;600&#34; height=&#34;150&#34; viewBox=&#34;0 0 600 150&#34; fill=&#34;none&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;
&lt;rect width=&#34;600&#34; height=&#34;150&#34; fill=&#34;none&#34;/&gt;
&lt;rect width=&#34;600&#34; height=&#34;150&#34; rx=&#34;8&#34; fill=&#34;#9EC2E2&#34;/&gt;
&lt;rect y=&#34;51&#34; width=&#34;600&#34; height=&#34;47&#34; fill=&#34;white&#34;/&gt;
&lt;path d=&#34;M307.08 75.396C306.436 74.64 306.044 73.688 305.96 72.68L305.792 70.916C305.596 68.312 303.664 66.156 301.2 65.568V65.4C301.2 64.616 300.584 64 299.8 64C299.016 64 298.4 64.616 298.4 65.4V65.568C295.936 66.156 294.004 68.312 293.808 70.916L293.64 72.68C293.556 73.688 293.192 74.64 292.52 75.424L291.456 76.684C290.532 77.832 290 79.26 290 80.716V81.5C290 81.892 290.308 82.2 290.7 82.2H308.9C309.292 82.2 309.6 81.892 309.6 81.5V80.716C309.6 79.26 309.068 77.832 308.144 76.684L307.08 75.396ZM308.2 80.8H291.4V80.716C291.4 79.596 291.792 78.476 292.548 77.58L293.612 76.32C294.452 75.312 294.928 74.08 295.04 72.792L295.18 71.028C295.376 68.676 297.42 66.8 299.8 66.8C302.18 66.8 304.224 68.676 304.42 71.028L304.56 72.792C304.672 74.08 305.176 75.312 305.988 76.32L307.052 77.58C307.808 78.476 308.2 79.596 308.2 80.716V80.8ZM303.02 83.74C302.712 83.516 302.264 83.572 302.012 83.88C300.948 85.308 298.652 85.308 297.56 83.88C297.336 83.572 296.888 83.516 296.58 83.74C296.272 83.964 296.216 84.412 296.468 84.72C297.252 85.784 298.484 86.4 299.8 86.4C301.116 86.4 302.348 85.784 303.132 84.72C303.384 84.412 303.328 83.964 303.02 83.74Z&#34; fill=&#34;black&#34;/&gt;
&lt;circle id=&#34;dot&#34; cx=&#34;306&#34; cy=&#34;72&#34; r=&#34;3.75&#34; fill=&#34;#DD2339&#34; stroke=&#34;white&#34; stroke-width=&#34;1.5&#34; opacity=&#34;0&#34;/&gt;
&lt;path id=&#34;flyout&#34; fill-rule=&#34;evenodd&#34; clip-rule=&#34;evenodd&#34; d=&#34;M300 97L306.235 106H409.2C413.68 106 415.921 106 417.632 106.872C419.137 107.639 420.361 108.863 421.128 110.368C422 112.079 422 114.32 422 118.8V151H317V148C317 146.895 316.105 146 315 146H188C186.895 146 186 146.895 186 148V151H177V118.8C177 114.32 177 112.079 177.872 110.368C178.639 108.863 179.863 107.639 181.368 106.872C183.079 106 185.32 106 189.8 106H293.765L300 97ZM188 121C186.895 121 186 121.895 186 123V128C186 129.105 186.895 130 188 130H333C334.105 130 335 129.105 335 128V123C335 121.895 334.105 121 333 121H188ZM187 133C186.448 133 186 133.448 186 134V138C186 138.552 186.448 139 187 139H365C365.552 139 366 138.552 366 138V134C366 133.448 365.552 133 365 133H187Z&#34; fill=&#34;white&#34; opacity=&#34;0&#34;/&gt;
&lt;g id=&#34;cursor&#34; transform=&#34;translate(100,100)&#34;&gt;
  &lt;path fill-rule=&#34;evenodd&#34; clip-rule=&#34;evenodd&#34; d=&#34;M303 75V91.015L306.225 87.8836L308.48 93.2243L312.085 91.6893L309.984 86.619H314.591L303 75Z&#34; fill=&#34;white&#34;/&gt;
  &lt;path fill-rule=&#34;evenodd&#34; clip-rule=&#34;evenodd&#34; d=&#34;M304 88.5951V77.4071L312.165 85.5901H308.471L310.751 91.0086L308.907 91.7826L306.537 86.1458L304 88.5951Z&#34; fill=&#34;black&#34;/&gt;
&lt;/g&gt;
&lt;animate xlink:href=&#34;#dot&#34; attributeName=&#34;opacity&#34; from=&#34;0&#34; to=&#34;1&#34; dur=&#34;0.1s&#34; begin=&#34;1s; cursor-move-out.end + 5s&#34; fill=&#34;freeze&#34; id=&#34;dot-appear&#34;/&gt;
&lt;animate xlink:href=&#34;#cursor&#34; attributeName=&#34;transform&#34; from=&#34;translate(100,100)&#34; to=&#34;translate(0,0)&#34; dur=&#34;0.25s&#34; begin=&#34;dot-appear.end + 0.2s&#34; fill=&#34;freeze&#34; id=&#34;cursor-move-in&#34;/&gt;
&lt;animate xlink:href=&#34;#flyout&#34; attributeName=&#34;opacity&#34; from=&#34;0&#34; to=&#34;0&#34; values=&#34;0; 1; 1; 0&#34; keyTimes=&#34;0; 0.1; 0.9; 1&#34; dur=&#34;0.4s&#34; begin=&#34;cursor-move-in.end + 0.2s&#34; fill=&#34;freeze&#34; id=&#34;flyout-toggle&#34;/&gt;
&lt;animate xlink:href=&#34;#dot&#34; attributeName=&#34;opacity&#34; from=&#34;1&#34; to=&#34;0&#34; dur=&#34;0.1s&#34; begin=&#34;flyout-toggle.begin + 0.15s&#34; fill=&#34;freeze&#34; id=&#34;dot-disappear&#34;/&gt;
&lt;animate xlink:href=&#34;#cursor&#34; attributeName=&#34;transform&#34; from=&#34;translate(0,0)&#34; to=&#34;translate(100,100)&#34; dur=&#34;0.25s&#34; begin=&#34;flyout-toggle.end + 0.2s&#34; fill=&#34;freeze&#34; id=&#34;cursor-move-out&#34;/&gt;
&lt;/svg&gt;
&lt;/figure&gt;


&lt;p&gt;We realized our customers were clicking once to show the popover, then immediately again to hide it.&lt;/p&gt;
&lt;p&gt;It turned out customers really just wanted to remove the red dot. &lt;strong&gt;Our metrics were lying:&lt;/strong&gt; positive engagement with notifications wasn’t going up as we had thought. Instead, we were annoying people.&lt;/p&gt;
&lt;p&gt;Like I typed above: we never got the notifications feature to the point we wanted and that’s OK. Some features are infinite projects, require continual investment and adjustments, and are made incrementally better over time.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>TIL about vim’s spellcheck</title>
      <link>https://nathanherald.com/posts/til/vim-spellcheck/</link>
      <pubDate>Thu, 21 May 2020 16:05:13 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/vim-spellcheck/</guid>
      <description>&lt;p&gt;I’ve been using vim for years, but I’ve never took the time to learn how to do spellcheck. Instead I always open [iA Writer][] if I need spellcheck. However, I have made way too many spelling errors in code comments or quick files and today I decided to learn how to check the spelling of a document.&lt;/p&gt;
&lt;p&gt;Today I added this to my &lt;code&gt;.vimrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-vim&#34; data-lang=&#34;vim&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;&amp;#34; spellcheck&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;command&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Sp&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;setlocal&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;spell&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;spelllang&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;en_us&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;command&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Spoff&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;setlocal&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;nospell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I only want spellcheck to be on in a single buffer at a time and not by default. After it’s enabled one uses &lt;code&gt;]s&lt;/code&gt; and &lt;code&gt;[s&lt;/code&gt; to navigate to the next and previous spelling error. With the cursor inside a problem one can use &lt;code&gt;z=&lt;/code&gt; to bring up a list of suggestions for replacement. It’s been great. Below is a screenshot of this file for example.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of this document in vim with spellcheck turned on&#34;
    width=&#34;828&#34;
    height=&#34;549&#34;
    src=&#34;https://nathanherald.com/posts/til/vim-spellcheck/screenshot@2x_hu209515097893001497.png&#34;&gt;
&lt;/figure&gt;


        
      </description>
    </item>
    <item>
      <title>TIL about :term &#43;&#43;curwin</title>
      <link>https://nathanherald.com/posts/til/vim-term-curwin/</link>
      <pubDate>Mon, 18 May 2020 13:43:07 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/vim-term-curwin/</guid>
      <description>&lt;p&gt;Often I want to open a new terminal in vim8 and until today I only knew how to open into a split window of some kind. Today I found out about &lt;code&gt;++curwin&lt;/code&gt; which instructs the terminal to overtake the current window. All the options for opening a new terminal can be found in the &lt;a href=&#34;https://vimhelp.org/terminal.txt.html&#34;&gt;vim help for terminal&lt;/a&gt;.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>TIL about :lcd in vim</title>
      <link>https://nathanherald.com/posts/til/lcd-vim/</link>
      <pubDate>Fri, 15 May 2020 14:25:59 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/til/lcd-vim/</guid>
      <description>&lt;p&gt;&lt;code&gt;:lcd&lt;/code&gt; can be used in &lt;code&gt;vim&lt;/code&gt; to change the directory for a single window (split) which &lt;a href=&#34;https://github.com/junegunn/fzf.vim&#34;&gt;fzf&lt;/a&gt; respects and any window/buffer I open from there inherits the same local directory.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using this to have one instance of vim open and keep different projects open in different tabs and it&amp;rsquo;s working well.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>This Video Has ___ Views</title>
      <link>https://www.youtube.com/watch?v=BxV14h0kFs0&amp;feature=emb_title</link>
      <pubDate>Thu, 09 Apr 2020 14:11:13 +0200</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/this-video-has-___-views/</guid>
      <description>
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/BxV14h0kFs0&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;A very good explanation of what it was like to create things in the Web 2.0 days and the openness at the time.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.youtube.com/watch?v=BxV14h0kFs0&amp;feature=emb_title&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Retail fonts · Klim Type Foundry</title>
      <link>https://klim.co.nz/retail-fonts/</link>
      <pubDate>Wed, 12 Feb 2020 10:29:00 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/klim-type-foundry/</guid>
      <description>&lt;p&gt;Klim Type Foundry makes some of the most beautiful typefaces. I really like Founders Grotesk, National 2, and Söhne.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of Founders Grotesk&#34;
    width=&#34;375&#34;
    height=&#34;292&#34;
    src=&#34;https://nathanherald.com/posts/links/klim-type-foundry/founders@3x_hu2213874615325338104.png&#34;&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of National 2&#34;
    width=&#34;375&#34;
    height=&#34;237&#34;
    src=&#34;https://nathanherald.com/posts/links/klim-type-foundry/national@3x_hu13755168899623579280.png&#34;&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of Söhne&#34;
    width=&#34;375&#34;
    height=&#34;239&#34;
    src=&#34;https://nathanherald.com/posts/links/klim-type-foundry/s%C3%B6hne@3x_hu16938046579458957095.png&#34;&gt;
&lt;/figure&gt;


        
          &lt;p&gt;
            &lt;a href=&#34;https://klim.co.nz/retail-fonts/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>A richer canvas</title>
      <link>https://markboulton.co.uk/journal/a-richer-canvas/</link>
      <pubDate>Wed, 22 Jan 2020 16:27:37 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/a-richer-canvas/</guid>
      <description>&lt;p&gt;Today I was re-reading this excellent article by &lt;a href=&#34;https://markboulton.co.uk/&#34;&gt;Mark Boulton&lt;/a&gt; from back in 2011: &lt;a href=&#34;https://markboulton.co.uk/journal/a-richer-canvas/&#34;&gt;A Richer Canvas&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&amp;rsquo;s my belief that in order to embrace designing native layouts for the web – whatever the device – we need to shed the notion that we create layouts from a canvas in. We need to flip it on its head, and create layouts from the content out.&lt;/p&gt;
&lt;/blockquote&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://markboulton.co.uk/journal/a-richer-canvas/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Realistic pillows sculpted from blocks of white marble</title>
      <link>https://www.thisiscolossal.com/2020/01/marble-pillows-by-hakon-anton-fageras/</link>
      <pubDate>Sat, 18 Jan 2020 15:54:09 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/realistic-pillows-sculpted-from-blocks-of-white-marble/</guid>
      <description>&lt;p&gt;These pillows crafted from marble by Håkon Anton Fagerås are stunning.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.thisiscolossal.com/2020/01/marble-pillows-by-hakon-anton-fageras/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Going public</title>
      <link>https://nathanherald.com/posts/going-public/</link>
      <pubDate>Wed, 15 Jan 2020 14:08:05 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/going-public/</guid>
      <description>&lt;p&gt;Today I&amp;rsquo;ve done something that I&amp;rsquo;ve wanted to do for some time: &lt;strong&gt;I&amp;rsquo;ve made the source code for my website public on &lt;a href=&#34;https://github.com/myobie/nathanherald.com&#34;&gt;github.com&lt;/a&gt; and you can see how the website is built and deployed.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m starting a new work journey, founding a new thing named &lt;a href=&#34;https://shareup.app&#34;&gt;Shareup&lt;/a&gt; with &lt;a href=&#34;https://anthonydrendel.com&#34;&gt;a colleague&lt;/a&gt;, and I&amp;rsquo;ve spent the past week working on a new website for it. I want to use a few specific programs and services:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Assemble the website with &lt;a href=&#34;https://gohugo.io&#34;&gt;hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Host the website using &lt;a href=&#34;https://zeit.co/now&#34;&gt;zeit&amp;rsquo;s now&lt;/a&gt; service&lt;/li&gt;
&lt;li&gt;Keep the website up to date by deploying every change with a &lt;a href=&#34;https://github.com/features/actions&#34;&gt;GitHub Actions&lt;/a&gt; workflow&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I also want to use the exact same setup for &lt;a href=&#34;https://nathanherald.com&#34;&gt;this here website&lt;/a&gt; and so I&amp;rsquo;ve been using my website as a test area to figure out how best to work with &lt;a href=&#34;https://gohugo.io&#34;&gt;hugo&lt;/a&gt;, &lt;a href=&#34;https://zeit.co/now&#34;&gt;now&lt;/a&gt;, and &lt;a href=&#34;https://github.com/features/actions&#34;&gt;actions&lt;/a&gt;. After struggling at this for a week, I&amp;rsquo;ve gotten it working and I wanted to document what I&amp;rsquo;ve learned so I remember (and maybe it&amp;rsquo;s helpful to you too).&lt;/p&gt;
&lt;p&gt;You can see the complete source for everything over on GitHub at &lt;a href=&#34;https://github.com/myobie/nathanherald.com&#34;&gt;github.com/myobie/nathanherald.com&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;tldr&#34;&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I def recommend &lt;a href=&#34;https://gohugo.io&#34;&gt;hugo&lt;/a&gt; for building static websites&lt;/li&gt;
&lt;li&gt;I def recommend &lt;a href=&#34;https://zeit.co/now&#34;&gt;now&lt;/a&gt; for hosting static websites&lt;/li&gt;
&lt;li&gt;I think &lt;a href=&#34;https://github.com/features/actions&#34;&gt;GitHub Actions&lt;/a&gt; are great and promising, but they are confusing and not always fully documented&lt;/li&gt;
&lt;li&gt;I spend way too much time making concessions so I can use &lt;a href=&#34;https://www.typography.com/fonts/whitney/overview&#34;&gt;this font&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This post is pretty long, so here are links to the different sections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#using-hugo&#34;&gt;Using hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#using-now&#34;&gt;Using now&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#hosting-the-font-somewhere-else&#34;&gt;Hosting the font somewhere else&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#generating-predictable-preview-urls&#34;&gt;Generating predictable preview URLs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#building-a-workflow-with-github-actions&#34;&gt;Building a workflow with GitHub Actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#putting-it-all-together&#34;&gt;Putting it all together&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;using-hugo&#34;&gt;Using hugo&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been making websites with &lt;a href=&#34;https://gohugo.io&#34;&gt;hugo&lt;/a&gt; for a while now so I can quickly setup and &lt;a href=&#34;https://github.com/myobie/nathanherald.com/blob/9f7b8e22b1876ab775d4c1e6e67b059121f60b35/config.toml&#34;&gt;configure&lt;/a&gt; a new hugo website pretty quickly. &lt;strong&gt;The feature of hugo I like the most is its speed.&lt;/strong&gt; It&amp;rsquo;s important to me that assembling my website is super quick. I also like that it&amp;rsquo;s a single binary which means I don&amp;rsquo;t have to figure out how to &amp;ldquo;install&amp;rdquo; anything.&lt;/p&gt;
&lt;h2 id=&#34;using-now&#34;&gt;Using &lt;a href=&#34;https://zeit.co/now&#34;&gt;now&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://zeit.co/now&#34;&gt;zeit&amp;rsquo;s now&lt;/a&gt; is a fantastic way to host a static website. Getting started with &lt;a href=&#34;https://zeit.co/now&#34;&gt;now&lt;/a&gt; is super easy, one simply runs &lt;code&gt;now&lt;/code&gt; in the terminal and it creates a project, deploys, and even copies the resulting URL to your clipboard. It&amp;rsquo;s almost too easy: I&amp;rsquo;ve accidentally created a new project a couple times by renaming a directory and zeit deciding &amp;ldquo;this is a new website&amp;rdquo; instead of &amp;ldquo;this is the same website in a new folder.&amp;rdquo;&lt;/p&gt;
&lt;h2 id=&#34;hosting-the-font-somewhere-else&#34;&gt;Hosting the font somewhere else&lt;/h2&gt;
&lt;p&gt;I have a strange and specific requirement for my website because of my &lt;a href=&#34;https://www.typography.com/fonts/whitney/overview&#34;&gt;font&lt;/a&gt;: &lt;strong&gt;the font I&amp;rsquo;ve chosen can only be loaded from certain URLs that I need to know ahead of time.&lt;/strong&gt; I also don&amp;rsquo;t have permission to &amp;ldquo;host the font&amp;rdquo; anywhere publicly (&lt;a href=&#34;https://github.com/myobie/nathanherald.com&#34;&gt;like this repo&lt;/a&gt;) and those two things meant I needed to do some extra work before I could make the source code for my website publicly viewable.&lt;/p&gt;
&lt;h3 id=&#34;putting-the-font-on-s3&#34;&gt;Putting the font on S3&lt;/h3&gt;
&lt;p&gt;I decided to zip up the fonts and put them in an S3 bucket. Then, when now is building the website, it can retrieve them using &lt;a href=&#34;https://s3tools.org/s3cmd&#34;&gt;s3cmd&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also, I learned a valuable lesson a while back: &lt;strong&gt;never create AWS resources using the UI.&lt;/strong&gt; Creating through the UI means it&amp;rsquo;s easy to forget how or what is setup.&lt;/p&gt;
&lt;p&gt;Instead, &lt;strong&gt;I use &lt;a href=&#34;https://www.terraform.io&#34;&gt;terraform&lt;/a&gt; for everything I need.&lt;/strong&gt; There is a pretty big upfront cost because I can never remember how exactly to create the terraform files.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I do create users in the UI because if one uses terraform then it&amp;rsquo;s easy to accidentally store their secret key in a &lt;code&gt;.tfstate&lt;/code&gt; file or something similar and I don&amp;rsquo;t want to risk it.&lt;/em&gt;
I created a user with zero permissions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I put some terraform &lt;a href=&#34;https://github.com/myobie/nathanherald.com/tree/9f7b8e22b1876ab775d4c1e6e67b059121f60b35/infra&#34;&gt;in a directory named infra&lt;/a&gt; which creates a bucket and gives it a policy where one user can only read from it. Then I &lt;a href=&#34;https://github.com/myobie/nathanherald.com/blob/9f7b8e22b1876ab775d4c1e6e67b059121f60b35/bin/install#L26-L36&#34;&gt;added some code to use &lt;code&gt;s3cmd&lt;/code&gt;&lt;/a&gt; to retrieve and unzip the font during the install step.&lt;/p&gt;
&lt;h2 id=&#34;generating-predictable-preview-urls&#34;&gt;Generating predictable preview URLs&lt;/h2&gt;
&lt;p&gt;When I&amp;rsquo;m writing a new blog post (&lt;a href=&#34;https://github.com/myobie/nathanherald.com/pulls/1&#34;&gt;like this one&lt;/a&gt;) I want to be able to see a &amp;ldquo;preview version&amp;rdquo; in my browser so I can make sure all the images look right, the font is working, etc, so I deploy a &amp;ldquo;preview site&amp;rdquo; for every &lt;a href=&#34;https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests&#34;&gt;pull request&lt;/a&gt; I make.&lt;/p&gt;
&lt;p&gt;Since I want to preview before everyone else sees it, I need to deploy the site somewhere other than &lt;code&gt;nathanherald.com&lt;/code&gt; which can break my font from loading if I can&amp;rsquo;t predict exactly what the URL of the preview site will be. To make this work I override the &amp;ldquo;base URL&amp;rdquo; for hugo to something I call the &lt;code&gt;PREVIEW_URL&lt;/code&gt;. I can dictate &lt;a href=&#34;https://github.com/myobie/nathanherald.com/blob/9f7b8e22b1876ab775d4c1e6e67b059121f60b35/.github/workflows/deploy.yml#L65&#34;&gt;the exact format of the URL&lt;/a&gt; and I can clear those URLs with my font provider.&lt;/p&gt;
&lt;h2 id=&#34;building-a-workflow-with-github-actions&#34;&gt;Building a workflow with GitHub Actions&lt;/h2&gt;
&lt;p&gt;The most difficult part when setting up my website was getting a workflow that works to deploy for every change. I knew exactly what I wanted to happen:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a deployment through the GitHub API to indicate a deploy is happening&lt;/li&gt;
&lt;li&gt;Mark the GitHub deployment as in progress&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;now&lt;/code&gt; passing in a &lt;code&gt;PREVIEW_URL&lt;/code&gt; so I can preview the site&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Alias&amp;rdquo; the now deployment to the &lt;code&gt;PREVIEW_URL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mark the GitHub deployment as complete&lt;/li&gt;
&lt;li&gt;Post a comment that says &amp;ldquo;🚀 your changes are deployed&amp;rdquo; linking to the &lt;code&gt;PREVIEW_URL&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I thought this wouldn&amp;rsquo;t be too difficult. &lt;em&gt;I was wrong.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can see my final &lt;a href=&#34;https://github.com/myobie/nathanherald.com/blob/d1ceec83d68a835b2aa423141a4214a5877f9292/.github/workflows/deploy.yml#L1&#34;&gt;deploy.yml&lt;/a&gt; workflow in the repo.&lt;/p&gt;
&lt;h3 id=&#34;build-one-action-to-perform-all-the-steps-which-didnt-work-out&#34;&gt;Build one action to perform all the steps (which didn&amp;rsquo;t work out)&lt;/h3&gt;
&lt;p&gt;My first attempt was to make one action that performs all these steps and I started that here: &lt;a href=&#34;https://github.com/myobie/deploy-now&#34;&gt;github.com/myobie/deploy-now&lt;/a&gt;. I found a javascript module named &lt;code&gt;now-client&lt;/code&gt; (which the &lt;code&gt;now&lt;/code&gt; program itself uses internally) and it exposes &lt;a href=&#34;https://github.com/zeit/now/blob/5872114c8734adfef61634eac026434b5a7c7be1/packages/now-client/src/index.ts#L9&#34;&gt;a &lt;code&gt;createDeployment&lt;/code&gt; function&lt;/a&gt; so it seemed like it was going to be easy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The biggest problem I had creating a GitHub Action is there is no local dev environment I know of to test the workflow on my computer.&lt;/strong&gt; Sure, there is a &lt;a href=&#34;https://github.com/actions/javascript-action/blob/c4da6cbeb333147c98df489667fb8849e97d7dd3/index.test.js#L19&#34;&gt;small test example&lt;/a&gt;, but it leaves &lt;strong&gt;a lot&lt;/strong&gt; to be desired.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s also sometimes lacking documentation: like how are arguments passed into your action? It turns out any &amp;ldquo;inputs&amp;rdquo; are passed in as ENV vars that begin with &lt;code&gt;INPUT_&lt;/code&gt; like &lt;code&gt;INPUT_TOKEN&lt;/code&gt; would be passed in for a step that has &lt;code&gt;with:&lt;/code&gt; followed by &lt;code&gt;token: abc&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://github.com/actions/toolkit/blob/e69833ed16500afaa7d137a9cf6da76fb8fb54da/packages/core/src/core.ts#L69&#34;&gt;javascript toolkit reads in these variables for you&lt;/a&gt;, so I decided that I was going to write everything in javascript because there might be other undocumented things I don&amp;rsquo;t know about that &lt;a href=&#34;https://github.com/actions/toolkit&#34;&gt;the toolkit&lt;/a&gt; is magically taking care of.&lt;/p&gt;
&lt;h4 id=&#34;using-ncc-to-package-everything-into-one-file&#34;&gt;Using ncc to package everything into one file&lt;/h4&gt;
&lt;p&gt;After deciding to use javascript I ran into my first roadblock: GitHub Actions doesn&amp;rsquo;t install your npm dependencies before running your code. Instead we are expected to pre-compile our javascript into one, dependency-free &lt;code&gt;.js&lt;/code&gt; file before pushing. &lt;strong&gt;This is super surprising.&lt;/strong&gt; The tool recommended to package up one&amp;rsquo;s javascript is &lt;a href=&#34;https://github.com/zeit/ncc&#34;&gt;&lt;code&gt;ncc&lt;/code&gt;&lt;/a&gt; (which is apparently a play on acronyms with &lt;code&gt;gcc&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/zeit/ncc&#34;&gt;ncc&lt;/a&gt; is easy to use and it natively supports &lt;a href=&#34;https://www.typescriptlang.org&#34;&gt;typescript&lt;/a&gt;, so I then decided that I&amp;rsquo;d consider using typescript. I setup a &lt;a href=&#34;https://github.com/myobie/deploy-now/blob/master/package.json#L16&#34;&gt;&amp;ldquo;package script task&amp;rdquo;&lt;/a&gt; and I made sure to setup &lt;a href=&#34;https://github.com/myobie/deploy-now/blob/master/pre-commit.sample&#34;&gt;a &lt;code&gt;pre-commit&lt;/code&gt; hook&lt;/a&gt; to package my javascript before I push any changes because I will forget.&lt;/p&gt;
&lt;h3 id=&#34;separating-the-steps-out-into-individual-actions&#34;&gt;Separating the steps out into individual actions&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;now-client&lt;/code&gt; ended up not working the same as just running &lt;code&gt;now&lt;/code&gt; (for example: rewrites in the &lt;code&gt;now.json&lt;/code&gt; file weren&amp;rsquo;t sent to zeit) and without being able to easily and quickly test changes locally, &lt;strong&gt;having a single big action ended up being too frustrating.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I decided to make smaller, focused actions where each one was small enough that I could more easily understand it and less easily break it. I&amp;rsquo;ve ended up with a little &lt;a href=&#34;https://github.com/shareup?utf8=%E2%9C%93&amp;amp;q=-action&amp;amp;type=&amp;amp;language=#org-repositories&#34;&gt;suite of actions&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;getting-the-name-of-the-current-branch&#34;&gt;Getting the name of the current branch&lt;/h3&gt;
&lt;p&gt;When GitHub triggers an Action it provides &lt;a href=&#34;https://help.github.com/en/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions&#34;&gt;a bunch of information&lt;/a&gt;, but none of that information reliably includes just the branch name. I want my preview site to include the branch name in its URL (so each branch has it&amp;rsquo;s own preview URL) so I wanted a way to find out just the branch name and have it set as an &amp;ldquo;output&amp;rdquo; so I could interpolate it later into the arguments to other actions.&lt;/p&gt;
&lt;p&gt;So, I made &lt;a href=&#34;https://github.com/shareup/output-git-metadata-action&#34;&gt;output-git-metadata-action&lt;/a&gt; which does exactly that: it &amp;ldquo;outputs&amp;rdquo; some git metadata, including the current branch name.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; one has to give the &amp;ldquo;step&amp;rdquo; an &lt;code&gt;id:&lt;/code&gt; in the yaml to be able to reference it later as &lt;code&gt;${{ steps.id.outputs.output_name }}&lt;/code&gt;. A step can have both an &lt;code&gt;id&lt;/code&gt; and a &lt;code&gt;name&lt;/code&gt;; &lt;code&gt;name&lt;/code&gt; is optional.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This action can be used like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;jobs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;yo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;runs-on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;ubuntu-latest&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;meta&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;shareup/output-git-metadata-action@master&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;print debugging info&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;branch: ${BRANCH}&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;BRANCH&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ steps.meta.outputs.branch }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;creating-the-github-deployment&#34;&gt;Creating the GitHub deployment&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://developer.github.com/v3/repos/deployments/&#34;&gt;Deployments&lt;/a&gt; in the GitHub API are very confusing to me. The documentation says &amp;ldquo;will trigger a &lt;code&gt;deploy&lt;/code&gt; event&amp;rdquo; but I am already doing the deployment, I just want the deployments UI of a pull request to light up and show what happened, like this:&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the GitHub Actions bot timeline entry for a deployment&#34;
    width=&#34;733&#34;
    height=&#34;63&#34;
    src=&#34;https://nathanherald.com/posts/going-public/github-actions-bot-timeline@2x_hu934874437331434408.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;I assume when I create a deployment through the API it does emit some &lt;code&gt;deploy&lt;/code&gt; event that no one is paying attention to. Seems wasteful. It would probably make more sense to separate the concept of &amp;ldquo;creating the deployment record&amp;rdquo; and &amp;ldquo;triggering the &lt;code&gt;deploy&lt;/code&gt; event.&amp;rdquo; It&amp;rsquo;s also very possible (likely) that I just don&amp;rsquo;t understand what I&amp;rsquo;m doing. &lt;code&gt;&amp;lt;/rant&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I made &lt;a href=&#34;https://github.com/shareup/create-deployment-action&#34;&gt;create-deployment-action&lt;/a&gt; to do what it says: create a deployment. It outputs the deployment&amp;rsquo;s id in case one need&amp;rsquo;s to reference it sometime later.&lt;/p&gt;
&lt;p&gt;And it &lt;strong&gt;doesn&amp;rsquo;t need one to generate a token&lt;/strong&gt; or anything: GitHub already provides a default token to every action which has read/write access to the current repository so I just use that inside the action code. I honestly don&amp;rsquo;t know where this is documented or how I found it, I think I might have found it while reading through the source of the &lt;a href=&#34;https://github.com/actions/checkout/blob/db41740e12847bb616a339b75eb9414e711417df/action.yml#L18&#34;&gt;checkout action&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;octokit&#34;&gt;&lt;code&gt;octokit&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;I found &lt;a href=&#34;https://github.com/octokit/rest.js&#34;&gt;&lt;code&gt;@octokit/rest&lt;/code&gt;&lt;/a&gt; confusing (and it not always having all it&amp;rsquo;s types for typescript working perfectly) and switched to &lt;a href=&#34;https://github.com/octokit/request.js&#34;&gt;&lt;code&gt;@octokit/request&lt;/code&gt;&lt;/a&gt; which I hadn&amp;rsquo;t really seen mentioned in any official docs. The request library is much simpler, has better &lt;code&gt;typescript&lt;/code&gt; types (since it&amp;rsquo;s written in typescript), and one can simply copy the documentation line like &lt;code&gt;POST /repos/:owner/:repo/issues/:number/labels&lt;/code&gt; &lt;a href=&#34;https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue&#34;&gt;straight from the GitHub API docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;updating-the-status-of-the-github-deployment&#34;&gt;Updating the status of the GitHub deployment&lt;/h3&gt;
&lt;p&gt;Creating a deployment is kinda meaningless and empty on its own: one has to &amp;ldquo;create a deployment status&amp;rdquo; to mark it as &amp;ldquo;in progress&amp;rdquo; or &amp;ldquo;success&amp;rdquo; or &lt;a href=&#34;https://developer.github.com/v3/repos/deployments/#parameters-2&#34;&gt;something else&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I made &lt;a href=&#34;https://github.com/shareup/create-deployment-status-action&#34;&gt;create-deployment-status-action&lt;/a&gt; for this purpose.&lt;/p&gt;
&lt;p&gt;At this point, I could see that I was on my way to creating my own little framework of actions and could write a &lt;code&gt;yml&lt;/code&gt; file like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;jobs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;deploy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;runs-on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;ubuntu-latest&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;output metadata&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;meta&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;shareup/output-git-metadata-action@master&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;create deployment&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;deployment&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;shareup/create-deployment-action@master&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;ref&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ steps.meta.outputs.sha }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;set deployment status to in_progress&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;shareup/create-deployment-status-action@master&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;deployment_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ steps.deployment.outputs.id }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;in_progress&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;echo example preview url&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo https://preview-for-branch-${{ steps.meta.outputs.branch }}.example.com&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;set deployment status to success&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;shareup/create-deployment-status-action@master&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;deployment_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ steps.deployment.outputs.id }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;success&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I like these super small and granular steps.&lt;/p&gt;
&lt;h3 id=&#34;deploying-with-now&#34;&gt;Deploying with &lt;code&gt;now&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;When I want to deploy with &lt;code&gt;now&lt;/code&gt; locally I just run &lt;code&gt;now&lt;/code&gt;. I wanted to make an action that does the same. There is a tool in the toolkit named &lt;a href=&#34;https://github.com/actions/toolkit/tree/e69833ed16500afaa7d137a9cf6da76fb8fb54da/packages/exec&#34;&gt;exec&lt;/a&gt; to run processes and I could use a &lt;code&gt;Dockerfile&lt;/code&gt; to make sure &lt;code&gt;now&lt;/code&gt; is pre-installed.&lt;/p&gt;
&lt;p&gt;So, I made &lt;a href=&#34;https://github.com/shareup/now-action&#34;&gt;now-action&lt;/a&gt; which executes &lt;code&gt;now&lt;/code&gt; and accepts inputs for almost every cli flag now accepts.&lt;/p&gt;
&lt;p&gt;Using &lt;a href=&#34;https://github.com/shareup/now-action/blob/7312114e9ea8610c160e005ed7d47d171f1c575c/Dockerfile&#34;&gt;a &lt;code&gt;Dockerfile&lt;/code&gt;&lt;/a&gt; took me a bit to understand because there are three paths I needed to keep track of: the path we are building the docker image from (the action&amp;rsquo;s repo), the path we are executing the action into (the website&amp;rsquo;s repo), and where the action&amp;rsquo;s files are stored inside the docker image (?).&lt;/p&gt;
&lt;p&gt;I built &lt;code&gt;FROM node:12&lt;/code&gt; so I knew node was already present and I used &lt;code&gt;npm&lt;/code&gt; to install &lt;code&gt;now&lt;/code&gt; globally during the build step.&lt;/p&gt;
&lt;p&gt;I decided to just make my own place to store stuff as &lt;code&gt;/now-action/&lt;/code&gt; and since I was already using &lt;code&gt;ncc&lt;/code&gt; for the other actions I kept that so I only needed to move &lt;code&gt;dist/index.js&lt;/code&gt; into the docker image for everything to work by then executing &lt;code&gt;/now-action/dist/index.js&lt;/code&gt; with &lt;code&gt;node&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since this action is completely isolated from the others, it can be used on it&amp;rsquo;s own like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;jobs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;now&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;runs-on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;ubuntu-latest&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;checkout&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;actions/checkout@v2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;now&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;shareup/now-action@master&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ secrets.zeit_token }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One has to set a secret on their repo to &lt;a href=&#34;https://zeit.co/account/tokens&#34;&gt;a token from zeit&lt;/a&gt; so it can use &lt;code&gt;now&lt;/code&gt; on your behalf.&lt;/p&gt;
&lt;h4 id=&#34;force-deploy&#34;&gt;Force deploy&lt;/h4&gt;
&lt;p&gt;now tries to be smart and doesn&amp;rsquo;t regenerate a website if it doesn&amp;rsquo;t see any changes, instead it will attempt to re-use a previous build. This caused me issues when deploying to production because it would re-use a build I had created for a preview website and then all my fonts stopped working because it expected the wrong URL. 😞&lt;/p&gt;
&lt;p&gt;I &lt;a href=&#34;https://github.com/myobie/nathanherald.com/blob/d1ceec83d68a835b2aa423141a4214a5877f9292/.github/workflows/deploy.yml#L63&#34;&gt;always pass &lt;code&gt;force: true&lt;/code&gt;&lt;/a&gt; to the &lt;a href=&#34;https://github.com/shareup/now-action&#34;&gt;now-action&lt;/a&gt; which tells now to always rebuild no matter what.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Honestly, I&amp;rsquo;ve done a lot of work for this font…&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;assigning-an-alias-to-a-now-deployment&#34;&gt;Assigning an alias to a now deployment&lt;/h3&gt;
&lt;p&gt;Running &lt;code&gt;now&lt;/code&gt; will create a new &amp;ldquo;deployment&amp;rdquo; and give it an autogenerated URL, which isn&amp;rsquo;t enough for me: I was to use my custom &lt;code&gt;PREVIEW_URL&lt;/code&gt;. now allows one to point a domain at a deployment and calls those &amp;ldquo;aliases.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;So, I made &lt;a href=&#34;https://github.com/shareup/now-alias-assign-action&#34;&gt;now-alias-assign-action&lt;/a&gt; which does exactly that.&lt;/p&gt;
&lt;p&gt;To alias a deployment with a preview URL including the branch name, I could now make a yaml like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;jobs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;now&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;runs-on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;ubuntu-latest&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;checkout&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;actions/checkout@v2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;meta&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;shareup/output-git-metadata-action@master&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;now&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;now&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;shareup/now-action@master&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ secrets.zeit_token }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;alias&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;shareup/now-alias-assign-action@master&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ secrets.zeit_token }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;deployment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ steps.now.outputs.deployment_url }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;alias&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;https://preview-for-${{ steps.meta.outputs.branch }}.example.now.sh&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Aliases should end with &lt;code&gt;${your-now-username}.now.sh&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;posting-a-comment&#34;&gt;Posting a comment&lt;/h3&gt;
&lt;p&gt;The last thing I wanted to do was to post a comment after a deployment with the URL so I could easily click it to see the preview website.&lt;/p&gt;
&lt;p&gt;I made &lt;a href=&#34;https://github.com/shareup/create-comment-action&#34;&gt;create-comment-action&lt;/a&gt; which will either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If a pull request is open, post a comment there&lt;/li&gt;
&lt;li&gt;Else, comment directly on the commit that triggered the action&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point I felt &lt;em&gt;good at making actions&lt;/em&gt; and I&amp;rsquo;m not sure if that is a good thing.&lt;/p&gt;
&lt;h2 id=&#34;putting-it-all-together&#34;&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&#34;https://github.com/myobie/nathanherald.com/blob/9f7b8e22b1876ab775d4c1e6e67b059121f60b35/.github/workflows/deploy.yml&#34;&gt;full yaml workflow&lt;/a&gt; in &lt;a href=&#34;https://github.com/myobie/nathanherald.com&#34;&gt;my website&amp;rsquo;s repo&lt;/a&gt; has some tricks to differentiate between production and staging.&lt;/p&gt;
&lt;p&gt;I can open a PR, preview my changes, and when I merge the PR the production website is deployed. A list of all the actions I created in the past week are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/shareup/output-git-metadata-action&#34;&gt;shareup/output-git-metadata-action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/shareup/create-deployment-action&#34;&gt;shareup/create-deployment-action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/shareup/create-deployment-status-action&#34;&gt;shareup/create-deployment-status-action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/shareup/now-action&#34;&gt;shareup/now-action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/shareup/now-alias-assign-action&#34;&gt;shareup/now-alias-assign-action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/shareup/create-comment-action&#34;&gt;shareup/create-comment-action&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/features/actions&#34;&gt;GitHub Actions&lt;/a&gt; are very powerful, but also super mysterious. Since I cannot execute workflows on my computer the feedback loop of pushing, waiting, checking the logs is way too slow. It&amp;rsquo;s still amazing and I am super happy now that it works, but getting there was way too difficult. I haven&amp;rsquo;t submitted these actions to &amp;ldquo;the marketplace&amp;rdquo; yet because I have to make an icon and a few other things to submit.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://zeit.co/now&#34;&gt;now&lt;/a&gt; is great. I really like it. It&amp;rsquo;s super simple and I will be using it more and more for static website hosting as well as &lt;a href=&#34;https://zeit.co/docs/v2/serverless-functions/introduction/&#34;&gt;serverless functions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://gohugo.io&#34;&gt;hugo&lt;/a&gt; is still my favorite way to build websites. It&amp;rsquo;s super fast and the way it organizes content is &lt;em&gt;alright&lt;/em&gt;. I love making websites and hugo mostly gets out of my way.&lt;/p&gt;
&lt;p&gt;While I spent way too much time on this setup, I now feel more confident to make changes, preview the change, and merge to deploy.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Elements of AI</title>
      <link>https://course.elementsofai.com/</link>
      <pubDate>Tue, 24 Dec 2019 09:48:15 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/elements-of-ai/</guid>
      <description>&lt;p&gt;A six-week series of free online courses created by Reaktor and the University of Helsinki for educating Finnish citizens about AI. Finland recently made them available online for everyone.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://course.elementsofai.com/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>The world&#39;s most remote buildings on YouTube 📺</title>
      <link>https://m.youtube.com/watch?v=wGS53t8ZbO8&amp;feature=share</link>
      <pubDate>Mon, 23 Dec 2019 08:55:12 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/the-worlds-most-remote-buildings/</guid>
      <description>&lt;p&gt;When I was young I remember watching the Discovery Channel often and learning all sorts of interesting things like this: &amp;ldquo;The World&amp;rsquo;s Most Remote Buildings.&amp;rdquo; YouTube is the new Discovery Channel, except it’s hard to know the quality or accuracy of a thing before watching it.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://m.youtube.com/watch?v=wGS53t8ZbO8&amp;feature=share&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>A ryokan</title>
      <link>https://nathanherald.com/posts/a-ryokan/</link>
      <pubDate>Wed, 18 Dec 2019 15:52:04 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/a-ryokan/</guid>
      <description>&lt;p&gt;The Wife and me and two friends pre-planned a trip to Japan many months in advance and I spent this last half year seriously looking forward to it. We recently spent two and half weeks in Japan on our first ever visit to the country! 🇯🇵 We went to a few places in Hokkaido, then to Kyoto, then accidentally to Osaka, and then to Tokyo. It was great to see so many different places and people and just enjoy being in a new place.&lt;/p&gt;
&lt;p&gt;One place we stayed, for a single night, really impacted me: a ryokan in Hokkaido named &lt;a href=&#34;https://zaborin.com/en/&#34;&gt;Zaborin&lt;/a&gt;. It&amp;rsquo;s in a town named Kutchan where it seemed many people train for competitive skiing. The pictures on the website are no joke, this is exactly what it looks like:&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://zaborin.com/en/&#34;&gt;
  &lt;img
    alt=&#34;Screenshot of the zaborin.com website&#34;
    width=&#34;1060&#34;
    height=&#34;733&#34;
    src=&#34;https://nathanherald.com/posts/a-ryokan/zaborin-website@2x_hu17093641737184595666.png&#34;&gt;
  &lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;The layout of the property and the rooms were all so pleasant and inspiring. The room we had was a nice mix of old and new: one area with a normal couch and TV and then a raised area with tatami mats for resting or enjoying a morning coffee and book.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of the elevated tatami mat area of the room&#34;
    width=&#34;1200&#34;
    height=&#34;900&#34;
    src=&#34;https://nathanherald.com/posts/a-ryokan/room@2x_hu3124584682418590257.jpg&#34;&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of a coffee cup and a water cup&#34;
    width=&#34;1200&#34;
    height=&#34;863&#34;
    src=&#34;https://nathanherald.com/posts/a-ryokan/morning-coffee@2x_hu7678712377271884274.jpg&#34;&gt;
  &lt;figcaption&gt;
    Morning coffee
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Every room has its own private baths with water constantly running from a hot spring: one inside and one outside. Even though it was very cold, sitting in the outside hot spring water and seeing the snowy trees and mountain was one of the most enjoyable things I&amp;rsquo;ve ever done.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of the outside stone bath full of hot spring water&#34;
    width=&#34;1200&#34;
    height=&#34;796&#34;
    src=&#34;https://nathanherald.com/posts/a-ryokan/outside-bath@2x_hu4518163147552335055.jpg&#34;&gt;
  &lt;figcaption&gt;
    The outside bath
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of the sun rising from behind a nearby mountain&#34;
    width=&#34;1200&#34;
    height=&#34;900&#34;
    src=&#34;https://nathanherald.com/posts/a-ryokan/sunrise@2x_hu16950455771255965360.jpg&#34;&gt;
  &lt;figcaption&gt;
    One of the most beautiful sunrises I&amp;rsquo;ve seen
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As you can see it snowed ⛄ We travelled up to Kutchan by train while it snowed and stormed out in the sea:&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of snow by the sea waves from a train&#34;
    width=&#34;1200&#34;
    height=&#34;900&#34;
    src=&#34;https://nathanherald.com/posts/a-ryokan/stormy-snowy-sea@2x_hu18403980099024835248.jpg&#34;&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;Train conductor in the snow&#34;
    width=&#34;1200&#34;
    height=&#34;900&#34;
    src=&#34;https://nathanherald.com/posts/a-ryokan/train-conductor@2x_hu3953622636986632835.jpg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;Enjoying the bath, the food, and reading was so relaxing and re-energizing – I really needed it! I also enjoyed our time in the other parts of Japan, and especially Kyoto, but I&amp;rsquo;ll be thinking about Zaborin a lot for a while.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>asipai ＋ Hibi Coffee in Kyoto</title>
      <link>https://andcoffee.net/shop/asipai-hibicoffee-kyoto/</link>
      <pubDate>Sat, 30 Nov 2019 15:26:17 +0900</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/asipai-hibicoffee-kyoto/</guid>
      <description>&lt;p&gt;Such a cute couple places together. I am posting this so I remember to come back here for curry.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://maps.apple.com/?address=%E3%80%92600-8146,%20%E4%BA%AC%E9%83%BD%E5%BA%9C%E4%B8%8B%E4%BA%AC%E5%8C%BA,%20%E4%BA%AC%E9%83%BD%E5%B8%82,%20%E4%B8%83%E6%9D%A1%E9%80%9A%E6%B2%B3%E5%8E%9F%E7%94%BA%E6%9D%B1%E5%85%A5%E6%9D%90%E6%9C%A8%E7%94%BA460&amp;amp;auid=5605049762898451087&amp;amp;ll=34.989593,135.764491&amp;amp;lsp=9902&amp;amp;q=Asipai%20Plus%20Hibi%20Coffee&amp;amp;_ext=ChkKBAgEEAoKBAgFEAMKBQgGEIgCCgQIChAAEiYp9lymFxd+QUAxAUl21kn4YEA5dDLMcz1/QUBBQ9K/qaP4YEBQBA%3D%3D&amp;amp;t=r&#34;&gt;Apple Maps link&lt;/a&gt;.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://andcoffee.net/shop/asipai-hibicoffee-kyoto/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>Music I love for traveling</title>
      <link>https://nathanherald.com/posts/travel-songs/</link>
      <pubDate>Fri, 22 Nov 2019 21:54:46 +0100</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/travel-songs/</guid>
      <description>&lt;p&gt;The past year I&amp;rsquo;ve been traveling an incredible amount and I&amp;rsquo;ve had a lot of opportunities to listen to different types of music. Below are my top, go-to albums for traveling. I prefer albums over playlists which also means I preferred the late &lt;a href=&#34;https://en.wikipedia.org/wiki/Rdio&#34;&gt;rdio&lt;/a&gt; ✌️. So before every flight I download a number of complete albums, always including:&lt;/p&gt;
&lt;h3 id=&#34;warm-on-a-cold-nighthttpsmusicapplecomusalbumwarm-on-a-cold-night1111358527--gone-are-the-dayshttpsmusicapplecomusalbumgone-are-the-days1059751428-by-honne&#34;&gt;&lt;a href=&#34;https://music.apple.com/us/album/warm-on-a-cold-night/1111358527&#34;&gt;Warm On A Cold Night&lt;/a&gt; &amp;amp; &lt;a href=&#34;https://music.apple.com/us/album/gone-are-the-days/1059751428&#34;&gt;Gone Are the Days&lt;/a&gt; by HONNE&lt;/h3&gt;
&lt;p&gt;OK, these are two albums, but I pretty much always listen to them back-to-back. The smooth beats, strings, and vocals really make any travel situation more bearable for me.&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://music.apple.com/us/album/warm-on-a-cold-night/1111358527&#34;&gt;
  &lt;img
    alt=&#34;Album art for Warm On A Cold Night&#34;
    width=&#34;250&#34;
    height=&#34;250&#34;
    src=&#34;https://nathanherald.com/posts/travel-songs/honne-0@2x_hu6472440680477879762.jpg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Listen on &lt;a href=&#34;https://music.apple.com/us/album/warm-on-a-cold-night/1111358527&#34;&gt;Apple Music&lt;/a&gt; or &lt;a href=&#34;https://open.spotify.com/album/0aWcnOSntyOxzNCxBksLW6&#34;&gt;Spotify&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;a href=&#34;https://music.apple.com/us/album/gone-are-the-days/1059751428&#34;&gt;
  &lt;img
    alt=&#34;Album art for Gone Are the Days&#34;
    width=&#34;250&#34;
    height=&#34;250&#34;
    src=&#34;https://nathanherald.com/posts/travel-songs/honne-1@2x_hu14050888184434297654.jpg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Listen on &lt;a href=&#34;https://music.apple.com/us/album/gone-are-the-days/1059751428&#34;&gt;Apple Music&lt;/a&gt; or &lt;a href=&#34;https://open.spotify.com/album/4VYRDmgRCtl3gpDbfD4Agv&#34;&gt;Spotify&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;dawn-chorushttpsmusicapplecomusalbumdawn-chorus1477510386-by-jacques-greene&#34;&gt;&lt;a href=&#34;https://music.apple.com/us/album/dawn-chorus/1477510386&#34;&gt;Dawn Chorus&lt;/a&gt; by Jacques Greene&lt;/h3&gt;
&lt;p&gt;It has surprised me how much I like this album and how often I can listen to it. It rolls and moves and that helps make a flight, train ride, or walk go by faster. I think I&amp;rsquo;m getting into Electronic; specifically house.&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://music.apple.com/us/album/dawn-chorus/1477510386&#34;&gt;
  &lt;img
    alt=&#34;Album art for Dawn Chorus&#34;
    width=&#34;250&#34;
    height=&#34;250&#34;
    src=&#34;https://nathanherald.com/posts/travel-songs/dawn-chorus@2x_hu12613916878298720646.jpg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Listen on &lt;a href=&#34;https://music.apple.com/us/album/dawn-chorus/1477510386&#34;&gt;Apple Music&lt;/a&gt; or &lt;a href=&#34;https://open.spotify.com/album/3fDOKKGXHv4CsGCOTv4W3z&#34;&gt;Spotify&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;mos-def--talib-kweli-are-black-starhttpsmusicapplecomusalbummos-def-talib-kweli-are-black-star328713-by-black-star&#34;&gt;&lt;a href=&#34;https://music.apple.com/us/album/mos-def-talib-kweli-are-black-star/328713&#34;&gt;Mos Def &amp;amp; Talib Kweli Are Black Star&lt;/a&gt; by Black Star&lt;/h3&gt;
&lt;p&gt;Arguably one of the best rap albums recorded, I often come back to this to hear some quality hip hop. Definition and Respiration will be relevant and catchy forever.&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://music.apple.com/us/album/mos-def-talib-kweli-are-black-star/328713&#34;&gt;
  &lt;img
    alt=&#34;Album art for Black Star&#34;
    width=&#34;250&#34;
    height=&#34;250&#34;
    src=&#34;https://nathanherald.com/posts/travel-songs/black-star@2x_hu2221487128808189119.jpg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Listen on &lt;a href=&#34;https://music.apple.com/us/album/mos-def-talib-kweli-are-black-star/328713&#34;&gt;Apple Music&lt;/a&gt; or &lt;a href=&#34;https://open.spotify.com/album/6GRzmk9UGL7odxprOPop1Q&#34;&gt;Spotify&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;habithttpsmusicapplecomusalbumhabit1466878928--lushhttpsmusicapplecomusalbumlush1355422978-by-snail-mail&#34;&gt;&lt;a href=&#34;https://music.apple.com/us/album/habit/1466878928&#34;&gt;Habit&lt;/a&gt; &amp;amp; &lt;a href=&#34;https://music.apple.com/us/album/lush/1355422978&#34;&gt;Lush&lt;/a&gt; by Snail Mail&lt;/h3&gt;
&lt;p&gt;Sure, these are again two albums, but I, again, almost always listen to these two in a row. &lt;strong&gt;These are my number one, go-to albums for travel.&lt;/strong&gt; It&amp;rsquo;s always a good time to listen to Snail Mail. They&amp;rsquo;ve made some albums which &lt;em&gt;sound like travel&lt;/em&gt; to me.&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://music.apple.com/us/album/habit/1466878928&#34;&gt;
  &lt;img
    alt=&#34;Album art for Habit&#34;
    width=&#34;250&#34;
    height=&#34;250&#34;
    src=&#34;https://nathanherald.com/posts/travel-songs/snail-mail-0@2x_hu9483012842890481752.jpg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Listen on &lt;a href=&#34;https://music.apple.com/us/album/habit/1466878928&#34;&gt;Apple Music&lt;/a&gt; or &lt;a href=&#34;https://open.spotify.com/album/1EjRNhbVxDEPXDwKmusDyj&#34;&gt;Spotify&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;a href=&#34;https://music.apple.com/us/album/lush/1355422978&#34;&gt;
  &lt;img
    alt=&#34;Album art for Lush&#34;
    width=&#34;250&#34;
    height=&#34;250&#34;
    src=&#34;https://nathanherald.com/posts/travel-songs/snail-mail-1@2x_hu3077858748456843203.jpg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Listen on &lt;a href=&#34;https://music.apple.com/us/album/lush/1355422978&#34;&gt;Apple Music&lt;/a&gt; or &lt;a href=&#34;https://open.spotify.com/album/2e48GqjEwCi87gQJanb1bf&#34;&gt;Spotify&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So good.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Petrichor by Artist Jean Jullien</title>
      <link>https://www.booooooom.com/2019/11/20/petrichor-by-artist-jean-jullien/</link>
      <pubDate>Thu, 21 Nov 2019 16:00:00 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/links/petrichor-by-jean-jullien/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Painting of water&#34;
    width=&#34;1200&#34;
    height=&#34;862&#34;
    src=&#34;https://nathanherald.com/posts/links/petrichor-by-jean-jullien/water_hu13301335057062277250.jpg&#34;&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;Paintings on display in a gallery&#34;
    width=&#34;1200&#34;
    height=&#34;800&#34;
    src=&#34;https://nathanherald.com/posts/links/petrichor-by-jean-jullien/gallery_hu97333280918370500.jpg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;Check out &lt;a href=&#34;https://www.instagram.com/jean_jullien/&#34;&gt;Jean Jullien on Instagram&lt;/a&gt; or checkout their work in person at &lt;a href=&#34;https://www.instagram.com/alice_bxl/&#34;&gt;Alice Gallery&lt;/a&gt; in Brussels until 11 Jan.&lt;/p&gt;

        
          &lt;p&gt;
            &lt;a href=&#34;https://www.booooooom.com/2019/11/20/petrichor-by-artist-jean-jullien/&#34;&gt;Check it out →&lt;/a&gt;
          &lt;/p&gt;
        
      </description>
    </item>
    <item>
      <title>I&#39;ve migrated off medium</title>
      <link>https://nathanherald.com/posts/moved-off-medium/</link>
      <pubDate>Thu, 21 Nov 2019 08:12:27 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/moved-off-medium/</guid>
      <description>&lt;p&gt;In 2013 I was tired of maintaining my own blog and started posting onto &lt;a href=&#34;https://medium.com/@myobie&#34;&gt;medium.com&lt;/a&gt; to save myself time. The first post was &lt;a href=&#34;https://medium.com/i-m-h-o/the-cost-of-education-63e3533b99c3&#34;&gt;The Cost of Education&lt;/a&gt; into a collection named IMHO back when anyone could make and join collections. I even became a paid member to help support &amp;ldquo;my blogging platform&amp;rdquo; so I wasn&amp;rsquo;t betting on a free service. Sadly, during the last couple years Medium has just been making things terrible – the worst of which is showing div popovers overtop my writing requesting readers to signup, sometimes not letting them read my writing if they don&amp;rsquo;t become paid members.&lt;/p&gt;
&lt;p&gt;I am no longer a paid member for Medium and I have migrated my writing over to &lt;a href=&#34;https://nathanherald.com/posts/&#34;&gt;my own website&lt;/a&gt;. When medium first launched it was a great example of good typography and easy publishing and it&amp;rsquo;s sad to see them ruin it.&lt;/p&gt;
&lt;h3 id=&#34;technology&#34;&gt;Technology&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m using &lt;a href=&#34;https://gohugo.io/&#34;&gt;hugo&lt;/a&gt; to build everything statically. I&amp;rsquo;m using &lt;a href=&#34;https://zeit.co/home&#34;&gt;now by zeit&lt;/a&gt; to build, deploy, and host my files. I&amp;rsquo;m using &lt;a href=&#34;https://ia.net/writer&#34;&gt;iA Writer&lt;/a&gt; to edit my files on both &lt;a href=&#34;macappstore://itunes.apple.com/app/id775737590?mt=12&#34;&gt;macOS&lt;/a&gt; and &lt;a href=&#34;macappstore://itunes.apple.com/app/id775737172?mt=12&#34;&gt;iOS&lt;/a&gt; and I push updates to my files with &lt;a href=&#34;https://git-scm.com&#34;&gt;git&lt;/a&gt; on macOS and &lt;a href=&#34;https://workingcopyapp.com&#34;&gt;Working Copy&lt;/a&gt; on iOS. Working Copy is a &amp;ldquo;file provider&amp;rdquo; on iOS which lets one edit the files from the git repo in any app like Writer or anything else with access to the Files app.&lt;/p&gt;
&lt;p&gt;Zeit creates a test website for every branch I push, so I can easily push a new post from my Mac or phone with a real, live preview and then merge when I&amp;rsquo;m happy – which is a great workflow for me. hugo is so fast that I never wait more than a couple seconds for everything to rebuild. I’m super happy with this setup 😎&lt;/p&gt;
&lt;h3 id=&#34;trying-to-go-full-phone&#34;&gt;Trying to go full phone&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m going to be traveling for a couple weeks without a laptop, so I prepared as much as I could to enable me to be able to do full posts entirely from my phone. Here is what that looks like:&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot or Working Copy shoeing the files for this website&#34;
    width=&#34;812&#34;
    height=&#34;375&#34;
    src=&#34;https://nathanherald.com/posts/moved-off-medium/working-copy@3x_hu11869178283096090964.png&#34;&gt;
  &lt;figcaption&gt;
    Working Copy showing the files in the git repository for this website
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot or iA Writer showing the code for this post&#34;
    width=&#34;812&#34;
    height=&#34;375&#34;
    src=&#34;https://nathanherald.com/posts/moved-off-medium/writer@3x_hu3434563263822412207.png&#34;&gt;
  &lt;figcaption&gt;
    iA Writer showing the markdown for this post
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the GitHub Mobile app&#34;
    width=&#34;375&#34;
    height=&#34;812&#34;
    src=&#34;https://nathanherald.com/posts/moved-off-medium/github@3x_hu7508327322976102913.png&#34;&gt;
  &lt;figcaption&gt;
    The fantastic new &lt;a href=&#34;https://github.com/mobile&#34;&gt;GitHub Mobile app&lt;/a&gt; is great so I&amp;rsquo;ve bookmarked this website&amp;rsquo;s repo there
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the GitHub Mobile app showing the Pull Request for this post&#34;
    width=&#34;375&#34;
    height=&#34;812&#34;
    src=&#34;https://nathanherald.com/posts/moved-off-medium/github-pr@3x_hu2105046920869695885.png&#34;&gt;
  &lt;figcaption&gt;
    Zeit&amp;rsquo;s now posts a &amp;ldquo;check status&amp;rdquo; and a comment to the Pull Request with the URL for the preview website where my new post is visible
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We&amp;rsquo;ll see how it goes posting with my phone. Inserting images is not the best…&lt;/p&gt;
&lt;h3 id=&#34;switching-costs&#34;&gt;Switching costs&lt;/h3&gt;
&lt;p&gt;It took way longer than I hoped or assumed to migrate away from Medium. The switching cost is real and high. I wish it weren&amp;rsquo;t the case, but publishing on the internet reliably is still not &amp;ldquo;easy.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;hugo&amp;rsquo;s shortcodes are always confusing and having to use them for inserting images means I cannot preview images directly in Writer. I may just edit the images in &lt;a href=&#34;https://www.pixelmator.com/ios/&#34;&gt;Pixelmator&lt;/a&gt; and then use the normal markdown image syntax.&lt;/p&gt;
&lt;p&gt;Zeit makes an assumption that builds are immutable and deterministic. While this is true for my website, there is one input that I consider a change that they do not: for the preview websites for every PR the domain for the website is not &lt;code&gt;nathanherald.com&lt;/code&gt; so I use an &lt;code&gt;ENV&lt;/code&gt; variable to inject a predictable domain for hugo to use. When I merge the PR, Zeit looks at the source code and says &amp;ldquo;I&amp;rsquo;ve seen all this before, so I&amp;rsquo;m not rebuilding again&amp;rdquo; and it promotes the version of the site built for something other than &lt;code&gt;nathanherald.com&lt;/code&gt; and that breaks all the images and colors and everything.&lt;/p&gt;
&lt;p&gt;I got annoyed and decided I would just move to &lt;a href=&#34;https://github.com/features/actions&#34;&gt;GitHub Actions&lt;/a&gt; and just build &lt;a href=&#34;https://gist.github.com/myobie/aafd9885aeea21aed19b85a60cacda4f&#34;&gt;wtf I want&lt;/a&gt;, but I have fallen prey to a bug in Actions that affects me because of my super-early access to the feature. It will be probably get fixed by next week…&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve created a workaround for the Zeit problem and I&amp;rsquo;ve sent them an email to ask them to consider the change in &lt;code&gt;ENV&lt;/code&gt; variables as a &amp;ldquo;change of inputs&amp;rdquo; and therefore trigger a new build…&lt;/p&gt;
&lt;p&gt;Good times™ as usual. I do love building for the web.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>How to install the macOS Catalina Beta alongside Mojave</title>
      <link>https://nathanherald.com/posts/legacy/how-to-install-the-macos-catalina-beta-alongside-mojave/</link>
      <pubDate>Mon, 17 Jun 2019 11:21:13 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/how-to-install-the-macos-catalina-beta-alongside-mojave/</guid>
      <description>&lt;p&gt;I’m sure you are interested in playing with &lt;a href=&#34;https://developer.apple.com/documentation/swiftui&#34;&gt;SwiftUI&lt;/a&gt; just like I am, but you are also hesitant to install the &lt;a href=&#34;https://www.apple.com/macos/catalina-preview/&#34;&gt;latest macOS Beta&lt;/a&gt; because it will surely mess up homebrew and generally cause inconvenience.  Even more, the live UI previews in XCode only work when running XCode inside the beta macOS…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you have a recent Mac with an APFS formatted drive then you can easily create a little area for the Beta without changing anything about your current setup.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.macobserver.com/tips/deep-dive/apfs-faq-partitions-volumes-afps-containers/&#34;&gt;APFS supports multiple “volumes” in the same “container.&lt;/a&gt;” A new volume can be created super quick on the fly, can be flexible in it’s size (with min/max settings on the GB reserved/used), and a “container” (think drive) shares all it’s space with all it’s volumes so you don’t have to know ahead of time how much space each volume really needs.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Below are the simple steps to create a volume, install macOS Mojave on it, then upgrade that volume’s OS to Catalina.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;1-create-avolume&#34;&gt;1. Create a volume&lt;/h3&gt;
&lt;p&gt;Search using Spotlight (⌘ space) for &lt;em&gt;Disk Utility&lt;/em&gt; and open it. Make sure your primary hard drive is selected in the left sidebar, then click the Plus icon in the toolbar to add a volume.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of Disk Utility showing a sheet to Add APFS volume to container&#34;
    width=&#34;1200&#34;
    height=&#34;789&#34;
    src=&#34;https://nathanherald.com/posts/legacy/how-to-install-the-macos-catalina-beta-alongside-mojave/1-XDH849DbmTwKvr5SON076A_hu16129875002253549815.png&#34;&gt;
  &lt;figcaption&gt;
    I named my new volume Beta
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You can choose to set &lt;em&gt;Size Options…&lt;/em&gt; to set a minimum reserve and a maximum usage for the volume. &lt;strong&gt;You will need at least 30 GB of space&lt;/strong&gt; to get the macOS beta + XCode Beta installed with some wiggle room. I set my Beta volume to reserve at least 20GB and restrict to a maximum of 100GB.&lt;/p&gt;
&lt;h3 id=&#34;2-install-macosmojave&#34;&gt;2. Install macOS Mojave&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;One cannot install macOS Catalina directly&lt;/strong&gt;: instead, one must upgrade from Mojave. Even if you are currently running macOS Mojave, you will need to download the macOS installer to use with the new volume.&lt;/p&gt;
&lt;p&gt;Using Spotlight (⌘ space) again, search for &lt;em&gt;App Store&lt;/em&gt; and open it. Search the App Store for &lt;em&gt;macOS&lt;/em&gt; and find the page for Mojave.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the macOS App Store app listing for macOS Mojave&#34;
    width=&#34;1200&#34;
    height=&#34;776&#34;
    src=&#34;https://nathanherald.com/posts/legacy/how-to-install-the-macos-catalina-beta-alongside-mojave/1-lbKgsosyxa7B6EmXbQdtqQ_hu2319844088406406738.png&#34;&gt;
  &lt;figcaption&gt;
    Use the App Store to “Get” macOS Mojave
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Click &lt;em&gt;Get&lt;/em&gt; and it will open the &lt;em&gt;Software Update&lt;/em&gt; preference pane in &lt;em&gt;System Preferences&lt;/em&gt;. Let it download the huge installer and it will eventually present you with the below Installer window. This will take a while…&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the macOS installer with the ability to select the destination volume&#34;
    width=&#34;1200&#34;
    height=&#34;966&#34;
    src=&#34;https://nathanherald.com/posts/legacy/how-to-install-the-macos-catalina-beta-alongside-mojave/1-Qbxuad8OQl6CjgB9YCnZaA_hu12122219858016592551.png&#34;&gt;
  &lt;figcaption&gt;
    Choose the new volume
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Be sure to choose the new volume and proceed to install. &lt;br&gt;
This will take a while…&lt;/p&gt;
&lt;h3 id=&#34;3-install-macoscatalina&#34;&gt;3. Install macOS Catalina&lt;/h3&gt;
&lt;p&gt;After installation is done and you’ve setup your new “computer” you will need to navigate to the &lt;a href=&#34;https://developer.apple.com/download/&#34;&gt;Apple Developer Downloads&lt;/a&gt; page to install the beta.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of Apple&amp;#39;s Beta Software Downloads webpage&#34;
    width=&#34;1200&#34;
    height=&#34;1004&#34;
    src=&#34;https://nathanherald.com/posts/legacy/how-to-install-the-macos-catalina-beta-alongside-mojave/1-UJ8lu3LY-vtBAL0mf6IEGQ_hu13295092284855961205.png&#34;&gt;
  &lt;figcaption&gt;
    Apple’s developer downloads page
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Choose &lt;em&gt;Install Profile&lt;/em&gt; which will open the &lt;em&gt;Software Update&lt;/em&gt; preference pane of &lt;em&gt;System Preferences&lt;/em&gt; again. This time, it will offer to download an installed for the macOS 10.15 Beta (which is Catalina).&lt;/p&gt;
&lt;p&gt;Install (download) it and it will eventually present an installation window.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the macOS installer&#34;
    width=&#34;1200&#34;
    height=&#34;966&#34;
    src=&#34;https://nathanherald.com/posts/legacy/how-to-install-the-macos-catalina-beta-alongside-mojave/1-5PSZ8X5F2F_KXuMe-MhGDw_hu17154490859232484135.png&#34;&gt;
  &lt;figcaption&gt;
    Install macOS Catalina just like Mojave before
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This will also take a while…&lt;/p&gt;
&lt;p&gt;You can still access your other volume from inside the macOS Catalina beta, so there isn’t any reason to copy over all your files or anything. This is great if you have an XCode project you want to work on or test out from the beta, but don’t want to have to transfer files all the time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I recommend installing applications separately onto Catalina and not trying to run applications from the Applications directory of the other volume.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Download the &lt;a href=&#34;https://developer.apple.com/download/#app&#34;&gt;XCode Beta&lt;/a&gt; and go create a new macOS or iOS project using SwiftUI. You choose to use SwiftUI either as a checkbox or dropdown in the &lt;em&gt;New Project sheet&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of Xcode with a panel for creating a new project&#34;
    width=&#34;1200&#34;
    height=&#34;803&#34;
    src=&#34;https://nathanherald.com/posts/legacy/how-to-install-the-macos-catalina-beta-alongside-mojave/1-qT-FmZHbCJfnYXSD4ueEkw_hu12862293458974461000.png&#34;&gt;
  &lt;figcaption&gt;
    One can choose SwiftUI when creating a new project
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;4-learn-aboutswiftui&#34;&gt;4. Learn about SwiftUI&lt;/h3&gt;
&lt;p&gt;I recommend going through the wonderful &lt;a href=&#34;https://developer.apple.com/tutorials/swiftui/tutorials&#34;&gt;SwiftUI Tutorials&lt;/a&gt; published by Apple. They walk through a ton of good stuff and cover a lot more than the basics. I really enjoyed going through it.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the Learn to Make Apps with SwiftUI webpage&#34;
    width=&#34;1200&#34;
    height=&#34;778&#34;
    src=&#34;https://nathanherald.com/posts/legacy/how-to-install-the-macos-catalina-beta-alongside-mojave/1-3b9HsS6TTn1kDt7AnGiOBA_hu13867472054840095176.png&#34;&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;5-restart-intomojave&#34;&gt;5. Restart into Mojave&lt;/h3&gt;
&lt;p&gt;You can restart back and forth between the OS’s anytime using the &lt;em&gt;Startup Disk&lt;/em&gt; preference pane in &lt;em&gt;System Preferences&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the Startup Disk preference pane&#34;
    width=&#34;1200&#34;
    height=&#34;792&#34;
    src=&#34;https://nathanherald.com/posts/legacy/how-to-install-the-macos-catalina-beta-alongside-mojave/1-BS9gN8E59TmcumZPRxxlrg_hu11964701652829882729.png&#34;&gt;
  &lt;figcaption&gt;
    You will need to “unlock” the Startup Disk preference pane by clicking the lock in the bottom left corner
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;em&gt;You can also keep&lt;/em&gt; &lt;strong&gt;&lt;em&gt;⌥ option&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;pressed when the computer is booting which will tell the computer to enumerate the bootable volumes and let you click which one to boot into.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;thats-it&#34;&gt;That’s it&lt;/h3&gt;
&lt;p&gt;Good luck. Comment if I got anything wrong. And have fun with SwiftUI, &lt;a href=&#34;https://developer.apple.com/documentation/combine&#34;&gt;Combine&lt;/a&gt;, and the new live UI previews in XCode. 🤖&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Build your MVP today</title>
      <link>https://nathanherald.com/posts/legacy/build-your-mvp-today-b31192e1c920/</link>
      <pubDate>Thu, 17 May 2018 14:13:58 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/build-your-mvp-today-b31192e1c920/</guid>
      <description>&lt;p&gt;Most software is simply a fancy spreadsheet or database. It could take you less than an hour to prototype an idea you have. Take the time and make it, if you can. Could you prototype your idea in &lt;a href=&#34;https://www.google.com/sheets/about/&#34;&gt;Google Sheets&lt;/a&gt;? &lt;strong&gt;You probably can.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;BTW, &lt;a href=&#34;https://www.youtube.com/watch?v=Ii3PDjJCCQ4&#34;&gt;Visicalc&lt;/a&gt; is probably the most important app ever made.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Input some information into a spreadsheet and then use &lt;a href=&#34;https://www.sheet2site.com&#34;&gt;&lt;strong&gt;sheet2site&lt;/strong&gt;&lt;/a&gt; to make a website from it. Most of the apps or websites you or I use every day are basically lists of data that we typed in. Maybe you’ve always wanted to make a list of the best places to visit in your home city/town: you can do it. You don’t need to know how to code.&lt;/p&gt;
&lt;p&gt;If you have some javascript skills, then maybe use &lt;a href=&#34;https://sheetsu.com&#34;&gt;&lt;strong&gt;Sheetsu&lt;/strong&gt;&lt;/a&gt; and now you have an API to build from. Are you good with React? Then maybe use &lt;a href=&#34;https://www.scrivito.com&#34;&gt;Scrivito&lt;/a&gt; and build some custom widgets to build a fancier website. Or use the awesome &lt;a href=&#34;https://www.contentful.com&#34;&gt;Contentful&lt;/a&gt; to collect all your information and build an app from that. Knowing how to code means you can get more built in an hour.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.lonelypage.io&#34;&gt;Build a landing page&lt;/a&gt; to market your idea. Post onto Twitter or Facebook or simply send an iMessage or Whatsapp message to some friends about it. You could use &lt;a href=&#34;https://www.wufoo.com&#34;&gt;Wufoo&lt;/a&gt; to collect feedback and/or payments.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.indiehackers.com&#34;&gt;Indie Hackers&lt;/a&gt; is a website full of stories of people who’ve created a simple prototype and now have some recurring income from it. Maybe take some time to read a few stories of things that worked and some that didn’t work to help get motivated. Yes, reading about things that didn’t work &lt;em&gt;is motivating&lt;/em&gt;. Not everything works out and that is OK. It is, in fact, likely. It’s part of the process.&lt;/p&gt;
&lt;p&gt;Replace one TV show or movie and build a prototype instead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some people don’t have a spare hour.&lt;/strong&gt; So, if you do, take an hour of your time and build the prototype of the idea they’ve been telling you about for them. Input some data into a spreadsheet, generate a website, and send them a URL and see what they think. It never hurts to practice building.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What are some tools or tips you have that have helped you try things our or build things quicker?&lt;/em&gt;&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>I still don’t know how to use a debugger</title>
      <link>https://nathanherald.com/posts/legacy/i-still-dont-know-how-to-use-a-debugger/</link>
      <pubDate>Mon, 14 May 2018 21:59:16 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/i-still-dont-know-how-to-use-a-debugger/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Illustration of plants and a bug with a little sign reading &amp;#39;no bugs&amp;#39;&#34;
    width=&#34;1200&#34;
    height=&#34;915&#34;
    src=&#34;https://nathanherald.com/posts/legacy/i-still-dont-know-how-to-use-a-debugger/1-1t7U5wQWkmueYNUO3o3CXA_hu10941954861293773891.jpeg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;Since I wrote my first javascript in the computer lab at my High School in VA using Windows 95, I’ve subscribed to the &lt;strong&gt;println debugging standard practice&lt;/strong&gt;. Of course then it was &lt;code&gt;alert&lt;/code&gt;. Either way, if there is a problem, add print statements everywhere.&lt;/p&gt;
&lt;p&gt;Well, &lt;strong&gt;it’s time I finally learn how to use a debugger.&lt;/strong&gt; The reason: &lt;em&gt;I wasted two entire days&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Do you use a debugger? Do you have any tips or tricks? How can I break the habit of &lt;code&gt;console.log&lt;/code&gt; or &lt;code&gt;IO.inspect&lt;/code&gt; or &lt;code&gt;println&lt;/code&gt; or whatever?&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;why-now&#34;&gt;Why now?&lt;/h3&gt;
&lt;p&gt;You see, I really like &lt;a href=&#34;https://www.erlang.org&#34;&gt;erlang&lt;/a&gt; and &lt;a href=&#34;https://elixir-lang.org&#34;&gt;elixir&lt;/a&gt;. I am playing around with spawning processes and communicating with them for a bit. Erlang has an insane concept called “&lt;a href=&#34;http://erlang.org/doc/tutorial/c_port.html&#34;&gt;ports&lt;/a&gt;” (nope, not those &lt;a href=&#34;https://en.wikipedia.org/wiki/Port_%28computer_networking%29&#34;&gt;ports&lt;/a&gt;) and after learning more about it I really like them. You use &lt;code&gt;stdout&lt;/code&gt; and &lt;code&gt;stdin&lt;/code&gt; as binary pipes to communicate back and forth. If anyone breaks either pipe, then everyone shuts everything down. It’s a great way to &lt;em&gt;know™&lt;/em&gt; you won’t have zombie processes.&lt;/p&gt;
&lt;p&gt;Well. &lt;code&gt;console.log&lt;/code&gt; writes to &lt;code&gt;stdout&lt;/code&gt;. Of course, right.&lt;/p&gt;
&lt;p&gt;I broke my binary communication protocol by attempting to debug a problem. Because I don’t know how to use a debugger. And, honestly, I &lt;em&gt;fixed&lt;/em&gt; this problem by continuing to be ignorant and appending log lines to a file instead.&lt;/p&gt;
&lt;p&gt;So yeah, I’m frustrated. Again, my lack of CS skills has caused me problems. Very rarely do I wish I were a “traditionally trained” programmer, but this is one of those times. I am going to finally learn this. Eventually. &lt;em&gt;When I get time.&lt;/em&gt;&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Puma RS-0</title>
      <link>https://nathanherald.com/posts/legacy/puma-rs-0/</link>
      <pubDate>Mon, 16 Apr 2018 12:08:40 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/puma-rs-0/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Two photos of the Puma RS-0 sneaker&#34;
    width=&#34;1200&#34;
    height=&#34;452&#34;
    src=&#34;https://nathanherald.com/posts/legacy/puma-rs-0/1-fUInbwuRrFG1IjDXUBhL-w_hu8930717035105493835.jpeg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;A reboot of the &lt;a href=&#34;https://www.nicekicks.com/puma-rs-computer-shoe-throwback-thursday/&#34;&gt;RS Computer Shoe&lt;/a&gt; (yes, for real), the &lt;a href=&#34;https://eu.puma.com/uk/en/rs-0&#34;&gt;Puma RS-0&lt;/a&gt; is a new “future-retro” sneaker and I am in love with them. I copped these over the weekend and I’ve really enjoyed the look and the comfort. They are super comfortable all day, but they are a bit heavy so I don’t think anyone will be running in these.&lt;/p&gt;
&lt;p&gt;The shoe is named the RS-0 because the design team wanted to “literally reboot it back to zero.” You can read a &lt;a href=&#34;https://hypebeast.com/2018/4/puma-heiko-desens-interview&#34;&gt;good interview with Heiko Desens&lt;/a&gt; from Puma about the shoe over on Hypebeast. They dropped &lt;a href=&#34;https://www.highsnobiety.com/p/puma-rs/&#34;&gt;two other RS-* shoes&lt;/a&gt; this weekend as well, but I only copped the zeros.&lt;/p&gt;
&lt;p&gt;I am super intrigued by the original Computer Shoe.&lt;/p&gt;
&lt;p&gt;You can read more about the &lt;a href=&#34;https://www.nicekicks.com/puma-rs-computer-shoe-throwback-thursday/&#34;&gt;history of the RS Computer Shoe&lt;/a&gt; over at Nice Kicks.&lt;/p&gt;
&lt;p&gt;They also dropped some special edition RS-0’s here in Berlin last week including &lt;a href=&#34;http://www.fortressofsolitude.co.za/puma-drops-rs-0-collection/&#34;&gt;Polaroid, Sega, and Roland collaborations&lt;/a&gt;. The Polaroids are 🔥&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Karhu Synchron Classic</title>
      <link>https://nathanherald.com/posts/legacy/karhu-synchron-classic/</link>
      <pubDate>Fri, 06 Apr 2018 21:48:32 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/karhu-synchron-classic/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of the Karhu Synchron Classic sneakers&#34;
    width=&#34;1200&#34;
    height=&#34;693&#34;
    src=&#34;https://nathanherald.com/posts/legacy/karhu-synchron-classic/1-TA5V6SBf8VtUEGhZKTm2ng_hu1310023517900665891.jpeg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;http://karhu.com/&#34;&gt;Karhu&lt;/a&gt; is a Finnish sneaker brand I wasn’t familiar with until recently. I’m getting more and more into sneakers and building up a collection. The latest cop for me are from the &lt;a href=&#34;http://karhu.com/linnutpack-p2/&#34;&gt;Linnut Pack — Part 2&lt;/a&gt; for which the colorways are inspired by bird watching. Yep.&lt;/p&gt;
&lt;p&gt;The history of the company is super interesting. You can read a full run-down over at &lt;a href=&#34;http://sneakers-magazine.com/history-check-over-100-years-of-karhu/&#34;&gt;Sneakers Magazine&lt;/a&gt;. They started in 1916 and their Olympic athletes were known as the Flying Finns. Another super crazy fact is &lt;a href=&#34;http://blog.karhu.com/three-stripes-trademark-sold-to-adidas/&#34;&gt;they sold the three stripes to Adidas&lt;/a&gt; for two bottles of whiskey and €1,600!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;According to Karhu, the two brands struck a historic deal that would make athletic trademark history: “Adi met the Karhu directors and invited them over to Germany. The result of the trip was that Karhu sold the &lt;a href=&#34;http://blog.karhu.com/three-stripes-trademark-sold-to-adidas/&#34;&gt;three stripes&lt;/a&gt; trademark to adidas for a small sum of money, around €1,600 in today’s terms, plus a couple bottles of good whiskey,” said the brand manager, adding: “That’s how the story goes, although we don’t have proof of the whiskey.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The unique lacing of the Synchron Classics is 🔥 and I’m excited to wear these around Berlin and during my upcoming trips to the States.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Are you into sneakers? Cop anything recently?&lt;/em&gt;&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>One week with the Atom text editor</title>
      <link>https://nathanherald.com/posts/legacy/one-week-with-the-atom-text-editor/</link>
      <pubDate>Mon, 26 Mar 2018 21:09:16 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/one-week-with-the-atom-text-editor/</guid>
      <description>&lt;p&gt;Every time I sit down to work I like things to be as similar as possible to the last. However, I do know that there are new tools, techniques, and ideas out there that would pass me by if I didn’t change things up every now and then. So this past week I’ve been using a different text editor.&lt;/p&gt;
&lt;p&gt;I normally use &lt;a href=&#34;https://www.vim.org&#34;&gt;vim&lt;/a&gt;. Well, I actually use &lt;a href=&#34;http://neovim.io&#34;&gt;neovim&lt;/a&gt;, which is a fork of vim. Well, really vim was a fork of &lt;a href=&#34;https://en.wikipedia.org/wiki/Vi&#34;&gt;vi&lt;/a&gt; long ago. The complexity of vim is so high, it’s even hard to explain it’s name, which version I use, etc. Vim is so confusing there is a question on Stack Overflow about it that has been viewed over 1.4 million times: “&lt;a href=&#34;https://stackoverflow.com/questions/11828270/how-to-exit-the-vim-editor&#34;&gt;How to exit the Vim editor?&lt;/a&gt;”&lt;/p&gt;
&lt;p&gt;There is &lt;a href=&#34;https://qz.com/990214/a-million-people-have-visited-this-web-page-explaining-how-to-close-vim-a-notoriously-difficult-text-editing-program/&#34;&gt;a Quartz article&lt;/a&gt; about that question and the history of vim being difficult. It’s over 30 years old, depending on which version you count. I never recommend vim to people. You really shouldn’t use it unless you want to be super unproductive for six months while you learn it.&lt;/p&gt;
&lt;p&gt;Either way, this past week I didn’t vim. Instead I used &lt;a href=&#34;https://atom.io&#34;&gt;Atom&lt;/a&gt;, which was created by &lt;a href=&#34;https://github.com&#34;&gt;GitHub&lt;/a&gt; and is based on their open source &lt;a href=&#34;https://electronjs.org&#34;&gt;Electron&lt;/a&gt; framework.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screentshot of the Atom editor Welcome Guide&#34;
    width=&#34;1200&#34;
    height=&#34;828&#34;
    src=&#34;https://nathanherald.com/posts/legacy/one-week-with-the-atom-text-editor/1-7muQ9MmmrJ8q2DnrQVZTZQ_hu3478855026856733488.png&#34;&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;the-good&#34;&gt;The Good&lt;/h3&gt;
&lt;p&gt;Atom has been around long enough to have plugins for everything. It is very unlikely that there isn’t a plugin for your favorite language. One can also customize the UI a lot: you can see above that I’ve hidden the normal title bar and file drawer and it looks really slick.&lt;/p&gt;
&lt;p&gt;There is an official plugin for editing concurrently with others named &lt;a href=&#34;https://teletype.atom.io&#34;&gt;Teletype&lt;/a&gt;. This was what really intrigued me to download and give Atom a spin: &lt;strong&gt;built in collaboration.&lt;/strong&gt; It works and I really like it. Every other real-time text editing plugin I’ve used has been awful to setup and not really worked well for long periods of time. This really seems to be done right.&lt;/p&gt;
&lt;p&gt;It’s super easy to install plugins and packages. The available color schemes are very good as well. The editor defaults are great: it’s the best default setup I’ve ever seen for a text editor. One can download this thing and get working immediately.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of source code in Atom&#34;
    width=&#34;1200&#34;
    height=&#34;849&#34;
    src=&#34;https://nathanherald.com/posts/legacy/one-week-with-the-atom-text-editor/1-7b7JphTAKxI9XJJQaxL0pg_hu6759178435835261097.png&#34;&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;the-bad&#34;&gt;The Bad&lt;/h3&gt;
&lt;p&gt;It can be slow.&lt;/p&gt;
&lt;p&gt;Electon (which Atom is built on top of) is a version of &lt;a href=&#34;https://www.chromium.org&#34;&gt;Chromium&lt;/a&gt; (the open source browser Chrome is built on) and the UI is all web technologies. Something about those super old text editors that is hard to beat is &lt;em&gt;when you type it shows up immediately.&lt;/em&gt; I’m not saying Atom is too slow for me: I have used it for an entire week and I enjoyed it. However, it really is noticeable sometimes.&lt;/p&gt;
&lt;p&gt;It also uses a ton of RAM. This doesn’t matter on my iMac, but on my MacBook this can be a problem. Last week I ended up running Slack (also Electron, so also a Chrome), Atom (another Electron), Microsoft Teams (also Electron, believe it or not), some docker containers, and then Mail and some websites in Safari and I noticed a big difference. Sure, just Atom alone isn’t really a problem. But having a lot of apps that each need the more RAM that most programs hurts my sad little laptop.&lt;/p&gt;
&lt;h3 id=&#34;the-ugly&#34;&gt;The Ugly&lt;/h3&gt;
&lt;p&gt;It just doesn’t work sometimes. When I try to find a file by typing a few characters, Atom will tell me that it is still indexing or that my project contains zero files. A restart of the app normally fixes the issue. This was a problem on my super fast iMac as well, so it’s not a performance issue. I took some screenshots.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of panel reading &amp;#39;Indexing project…&amp;#39;&#34;
    width=&#34;1200&#34;
    height=&#34;234&#34;
    src=&#34;https://nathanherald.com/posts/legacy/one-week-with-the-atom-text-editor/1-8j9R2Nfm8svRDxPdzUYosg_hu17259847707321534028.png&#34;&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of panel reading &amp;#39;Project is empty&amp;#39;&#34;
    width=&#34;1200&#34;
    height=&#34;219&#34;
    src=&#34;https://nathanherald.com/posts/legacy/one-week-with-the-atom-text-editor/1-hUkAEyrUnIcsZwPIlrHg6A_hu15035327600462303676.png&#34;&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;When I use vim I have &lt;a href=&#34;https://github.com/BurntSushi/ripgrep&#34;&gt;ripgrep&lt;/a&gt; feed a file list into &lt;a href=&#34;https://github.com/junegunn/fzf&#34;&gt;fzf&lt;/a&gt; and it works every time and works immediately. There is no caching: it just is able to scan the entire project very quickly every time. It’s easy to get spoiled by things working so fast and it’s really frustrating that Atom doesn’t work at least similarly fast (and sometimes not at all).&lt;/p&gt;
&lt;h3 id=&#34;so-whats-the-verdictthen&#34;&gt;So, what’s the verdict then?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Atom is really great.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Just the statement “I have ripgrep feed a file list into fzf” is super complex and confusing and that’s what it’s like using vim. I deal with configuring stuff like that all the time and it does get tiring.&lt;/p&gt;
&lt;p&gt;Atom is a single thing to install and it generally works. It includes collaboration features, it has plugins for anything that are easily installed from inside the program, and it has the best defaults of any text editor I’ve ever used.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I recommend trying it out.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What do you think of Atom? How often do you try other editors or tools?&lt;/p&gt;
&lt;p&gt;I’m going to back to vim. Vim is a problem: once you get into it it’s hard to ever use anything else. I’m glad I tried Atom and next time I need to collaborate on some code with someone I will fire it up immediately.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Twenty-second Postmortem</title>
      <link>https://nathanherald.com/posts/legacy/twenty-second-postmortem/</link>
      <pubDate>Fri, 22 Sep 2017 16:45:06 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/twenty-second-postmortem/</guid>
      <description>&lt;p&gt;With lots to do and not enough time to do everything, it can seem impossible to document progress or keep track of how things might have gone. Some items seem small, so we just complete them and move on. It can, however, be incredibly valuable and sometimes necessary to remember what actually happened, when, and maybe why a decision was made.&lt;/p&gt;
&lt;p&gt;I have a terrible memory, so I use a to-do list app to keep track of what I need to do. This past year I’ve tried to not just use my to-do list to keep track of what’s left to do, but also to document what’s been done with more detail than I did before. If you use a calendar, a notes app, or a sheet of paper you can do the same thing.&lt;/p&gt;
&lt;p&gt;Before marking something as complete, take 20 seconds to write down what went right, what went wrong, why, and then move on. Those 20 seconds can end up being super valuable down the road. It is also good practice to be regularly reflective about progress. I just input my thoughts into the “note” field for the to-do item before checking it off. I like this because the documentation is attached to the original work item.&lt;/p&gt;
&lt;p&gt;Here’s an example implemented in a few apps:&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshots of four different apps showing my notes attached to a task&#34;
    width=&#34;1200&#34;
    height=&#34;533&#34;
    src=&#34;https://nathanherald.com/posts/legacy/twenty-second-postmortem/1-aIcmIkQjAjC-wtfW4o1DtQ_hu16525837836390052296.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;I recommend trying this or something similar for a while. It can be really nice to look back and remind yourself why something worked out differently than originally planned, who you talked to about a topic before marking it done, or just to see that everything did indeed work out as expected.&lt;/p&gt;
&lt;p&gt;Do you have any tricks to keep document progress?&lt;br&gt;
Any recommendations for me?&lt;/p&gt;
&lt;p&gt;🏁&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>A quick tip to help remember where you left off</title>
      <link>https://nathanherald.com/posts/legacy/a-quick-tip-to-help-remember-where-you-left-off/</link>
      <pubDate>Fri, 28 Jul 2017 07:46:34 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/a-quick-tip-to-help-remember-where-you-left-off/</guid>
      <description>&lt;p&gt;If your short-term memory isn’t the best, like me, then you probably have a few tricks to help recall information easier. When I’m programming, working on a spreadsheet, or anything on a computer and I need to leave it in an unfinished state: I will &lt;strong&gt;take a screenshot&lt;/strong&gt;. &lt;a href=&#34;http://www.macworld.com/article/3178609/data-center-cloud/how-to-take-best-advantage-of-icloud-desktop-and-documents-across-macs.html&#34;&gt;macOS now supports syncing your Desktop to all your devices&lt;/a&gt;, so that screenshot ends up everywhere automatically.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;1-6tpKvBdekv6AWu5w6jRSEQ.png&#34; alt=&#34;Screenshot of logging output&#34;&gt;&lt;/p&gt;
&lt;p&gt;This morning I noticed a screenshot waiting for me of some code exception. I assume I wanted myself to fix this first-thing today. &lt;em&gt;After making coffee, of course.&lt;/em&gt; Taking a screenshot, making a note in a notes app, or emailing yourself can really help reconnect you to what you were thinking.&lt;/p&gt;
&lt;p&gt;🏁&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>A newsletter</title>
      <link>https://nathanherald.com/posts/legacy/a-newsletter/</link>
      <pubDate>Tue, 25 Jul 2017 08:26:39 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/a-newsletter/</guid>
      <description>&lt;p&gt;Soon, I will start sending &lt;a href=&#34;https://www.getrevue.co/profile/myobie&#34;&gt;a newsletter&lt;/a&gt; with interesting stories, analysis, and links to things that I find interesting. I’ll also include some paragraphs and links to what I’ve been up to. There will definitely be content about startups, Berlin, and tech. There will also be a bit about art and entertainment and how they intersect with tech.&lt;/p&gt;
&lt;p&gt;I won’t be posting these emails as blog articles: it is meant to be consumed as a newsletter. The frequency will be every two weeks at first: every other Tuesday around 5pm GMT. I’ll be sending out the first issue 1 Aug.&lt;/p&gt;
&lt;p&gt;The name of the newsletter is “Tuesday TEA time.” Yeah. &lt;em&gt;(Tech, entertainment, and art.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I am using &lt;a href=&#34;https://www.getrevue.co&#34;&gt;Revue&lt;/a&gt; to write and send the newsletter. So far, so good. Their support has been super responsive (thanks Martin).&lt;/p&gt;
&lt;p&gt;You can subscribe to my newsletter by following the link below to &lt;a href=&#34;https://www.getrevue.co/profile/myobie&#34;&gt;my Revue profile page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I look forward to sending you some interesting emails in the near future.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of a postbox&#34;
    width=&#34;800&#34;
    height=&#34;1200&#34;
    src=&#34;https://nathanherald.com/posts/legacy/a-newsletter/1-EHUBOUohExFZ1RgwJ4gIww_hu14115635203947779099.jpeg&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;🏁&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Design a Minimal Product Roadmap instead of building an MVP</title>
      <link>https://nathanherald.com/posts/legacy/design-a-minimal-roadmap-instead-of-building-an-mvp/</link>
      <pubDate>Tue, 18 Jul 2017 09:12:55 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/design-a-minimal-roadmap-instead-of-building-an-mvp/</guid>
      <description>&lt;p&gt;Often when advising a founder or colleague, we end up talking through what an MVP could look like. Not everyone agrees on the definition of “viable,” however, and this always creates confusion. Lately, I’ve found it useful to take a slightly different view to find a path from zero to one customer. I call it a Minimal Product Roadmap.&lt;/p&gt;
&lt;p&gt;The different stages of a Minimal Product Roadmap should be close to &lt;em&gt;(from the potential customer’s perspective)&lt;/em&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;I understand what you want to build.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I can use what you have built on my device. Cool.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I can see &lt;em&gt;exactly&lt;/em&gt; how what you have built will help me.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I would pay for what you have built.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I love what you have built. ❤️&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;The order of 4 and 5 can change depending on the product. People will pay for things they don’t love and people might not pay even tho they love it. Also your product might be free. C’est la vie.&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Side-note: I have heard people say “Minimum Lovable Product” before to emphasize the importance of getting to that last step. It sounds hokey, but it makes sense.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Focusing on the core problem and finding a way to prove that &lt;em&gt;your idea&lt;/em&gt; is &lt;em&gt;the best way&lt;/em&gt; to solve that problem is a tough challenge. It’s even harder if you need to build every feature and implement every detail. Instead only build what can get you to the stages I listed above. And for &lt;em&gt;only one problem&lt;/em&gt; at a time.&lt;/p&gt;
&lt;p&gt;Laying out small steps means you can get feedback at each step, possibly re-evaluate where you are headed, then arrive at a better place in the end. Getting feedback is crucial to building any product. Having a small plan of small steps means you will start getting feedback much sooner. You might end up building three, four, or more versions before arriving at one that is understandable, usable, and lovable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So many MVPs should be a Google Form that inserts a row into a Spreadsheet.&lt;/strong&gt; The founders should have a separate tab with charts and graphs for their “analytics dashboard.” Google Docs has an API and you can &lt;a href=&#34;https://www.npmjs.com/package/google-spreadsheets&#34;&gt;hit it with javascript&lt;/a&gt; to build a frontend. Need something fancier? Then use &lt;a href=&#34;https://www.wufoo.com&#34;&gt;Wufoo&lt;/a&gt;. Wufoo can even accept payments, provide tracking and analytics, and basically be your “customer database.”&lt;/p&gt;
&lt;p&gt;Sure, lots of ideas are bigger than a web form, but they are probably not as big as you are thinking right now. It is one of the most difficult initial challenges for a founder to stay small, to resist building that extra thing, to instead polish the one feature you truly need until it’s the best it could be. I suffer from this all the time too, “I need to do this and this and this before anyone will get it!” It’s just not true: &lt;em&gt;you don’t need to do everything.&lt;/em&gt; It’s easy to procrastinate by doing some easy work, like designing more and more screens, and avoiding the hard work of building a lovable solution to a single hard problem.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://adaptivepath.org&#34;&gt;Adaptive Path&lt;/a&gt; has talked about the “cupcake model” which is a great way to visualize this:&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&#34;https://speckyboy.com/building-minimum-viable-products-spotify/&#34;&gt;
  &lt;img
    alt=&#34;Illustration of growing from cupcake, to cake, then to wedding cake size&#34;
    width=&#34;500&#34;
    height=&#34;199&#34;
    src=&#34;https://nathanherald.com/posts/legacy/design-a-minimal-roadmap-instead-of-building-an-mvp/1-AYQw1M9iGUeQMw2jbFqCTg_hu4890214936043324375.jpeg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Photo from &lt;a href=&#34;https://speckyboy.com/building-minimum-viable-products-spotify/&#34;&gt;Speckboy&lt;/a&gt; who got it from &lt;a href=&#34;http://cupcakepictures.com/2014/04/the-cake-model-of-product-planning/&#34;&gt;cupcakepictures.com&lt;/a&gt; which seems to be gone now.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I love this analogy because a cupcake can be super detailed if one wanted. &lt;strong&gt;A cupcake can be something a customer loves and pays for without being full-size.&lt;/strong&gt; You don’t need to build the cake, just build the smallest amazing bite. A &lt;a href=&#34;http://sprinkles.com/locations/washington-dc/georgetown&#34;&gt;cupcake place in Washington DC&lt;/a&gt; I used to frequent sells “icing shots.” &lt;em&gt;Can you build the icing shot version of your product?&lt;/em&gt; Give your customers a hit of sugar as soon as you can. And don’t waste your time building a large vanilla sponge cake with no icing or decoration, that’s not lovable.&lt;/p&gt;
&lt;p&gt;You’ve probably seen a graphic or two showing the progression from a skateboard to a bicycle to a car, instead of building a wheel then axle then body, etc. The first time I saw it was from Spotify:&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of a slide titled &amp;#39;How Spotify builds a product&amp;#39;&#34;
    width=&#34;475&#34;
    height=&#34;510&#34;
    src=&#34;https://nathanherald.com/posts/legacy/design-a-minimal-roadmap-instead-of-building-an-mvp/1-TYzZEL4cn3pjPo-J9akwyQ_hu14716428074456210634.jpeg&#34;&gt;
  &lt;figcaption&gt;
    Found this image over at &lt;a href=&#34;https://www.quora.com/Where-is-the-line-for-MVPs-minimum-viable-products-What-are-your-principals-guidelines-for-defining-your-MVP-in-particular-where-to-stop-building-too-much&#34;&gt;Quora&lt;/a&gt;.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This is also a great way to reorient how you think. Maybe your startup is about “moving people from point A to B” and so you start building a car. However, a skateboard fulfills your mission statement, so why build a whole car? Are you about cars or about “personal transportation?” Maybe your customers want to get some exercise? Maybe your customers want to rent the vehicle and not own? Maybe your customers have to provide some elbow-grease at first to help you along at first? That’s fine. &lt;strong&gt;You cannot learn what your customers really want until you are in front of them.&lt;/strong&gt; A Minimal Product Roadmap gets you in front of them faster.&lt;/p&gt;
&lt;p&gt;And, more selfishly, it feels much better to ship an amazing cupcake than a mediocre full-size cake. Receiving positive, loving customer emails is a great motivator, especially when it’s early and everything is super uphill. Be encouraged: it’s possible to think small, to build something minimal and lovable, and to have fun doing it. 😀&lt;/p&gt;
&lt;p&gt;I love talking to and advising early startups and helping them design a Minimal Product Roadmap. If you wanna chat then &lt;a href=&#34;http://nathanherald.com&#34;&gt;reach out to me&lt;/a&gt;. Also, if you have any feedback or criticism about this article then shoot me an email.&lt;/p&gt;
&lt;p&gt;🏁&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>On queueing</title>
      <link>https://nathanherald.com/posts/legacy/on-queueing/</link>
      <pubDate>Thu, 29 Jun 2017 10:28:44 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/on-queueing/</guid>
      <description>

    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/IPxBKxU8GIQ?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;When building a web app, or any kind of distributed system, things will probably get backed up. Most people I’ve worked with are surprised if/when this happens the first time. I know I was surprised when I first dealt with it. So remember: &lt;em&gt;if you use a queue for something, it will queue up eventually.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You might be queueing stuff and not realize it. If you are making HTTP requests from your code then you probably have a request pool. Making a database call probably checks out a connection from a pool or waits in a queue for the next available connection.&lt;/p&gt;
&lt;p&gt;At Wunderlist we saw a seemingly simple change have big repercussions when it came to request queueing. Originally most of the Wunderlist backend apps were written in rails and used &lt;a href=&#34;https://rubygems.org/gems/unicorn/versions/4.9.0&#34;&gt;unicorn&lt;/a&gt;. We really wanted to use &lt;a href=&#34;https://rubygems.org/gems/puma/versions/3.9.1&#34;&gt;puma&lt;/a&gt; to use less memory from forking fewer processes.&lt;/p&gt;
&lt;p&gt;According the unicorn documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Load balancing is done entirely by the operating system kernel. Requests never pile up behind a busy worker process.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This sounds fine. We &lt;em&gt;assumed&lt;/em&gt; that’s just how people make HTTP servers. It’s not. A subtle detail about puma is that it queues requests &lt;a href=&#34;https://github.com/puma/puma/blob/master/lib/puma/thread_pool.rb#L26&#34;&gt;inside ruby code&lt;/a&gt;, which means it’s up to the ruby code to manage timeouts and stuff like that. It’s incoming queue is basically unbounded: if you ask it to take a connection, then it will. So it’s up to you to not give it a connection if it’s extremely busy or backed up. &lt;em&gt;Puma is an extremely well written ruby library and you should read through it’s source sometime.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This reminded me of this article about &lt;a href=&#34;http://devblog.thinkthroughmath.com/blog/2013/02/27/managing-request-queuing-with-rails-on-heroku/&#34;&gt;queueing with rails on heroku&lt;/a&gt; and it’s worth a read. It pays to have data, graphs, etc to be able to really know what is happening.&lt;/p&gt;
&lt;p&gt;We did transition some less critical apps to use puma, but in the end almost all the critical path apps were rewritten in scala (using &lt;a href=&#34;https://playframework.com&#34;&gt;play&lt;/a&gt;) and so we traded one set of problems for another.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://unsplash.com/search/line?photo=VY1EDHABTDE&#34;&gt;&lt;img src=&#34;1-eGTetytn3pVJh6BwfbHrlQ.jpeg&#34; alt=&#34;People waiting in a line&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;backing-up-abit&#34;&gt;Backing up a bit&lt;/h3&gt;
&lt;p&gt;Recently I read this amazing article by &lt;a href=&#34;https://twitter.com/mononcqc/&#34;&gt;Fred Hérbert&lt;/a&gt; about &lt;a href=&#34;http://ferd.ca/handling-overload.html&#34;&gt;Handling Overload&lt;/a&gt;. &lt;strong&gt;You should read it too.&lt;/strong&gt; The amount of information in that post is overwhelming.&lt;/p&gt;
&lt;p&gt;The basic rule at the beginning is what you need to know: &lt;a href=&#34;https://en.wikipedia.org/wiki/Little%27s_law&#34;&gt;Little’s Law&lt;/a&gt;. From the article:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[T]he capacity of your system is going to be how long a task takes to go through it, [multiplied] with how many can be handled at once…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If we can answer 100 requests “simultaneously” and a request takes 200ms on average to complete, then any time there are over 500 requests per second we either queue up, need to apply back-pressure, or drop some requests.&lt;/p&gt;
&lt;p&gt;Queueing up is the worst of the three choices IMO. Sitting in a queue means that the average time to process one item is increasing, even though we are not taking longer to do the real work. Every time this happens we start losing capacity slowly. As the average time to handle a request goes up, our ability to answer the newest incoming request before it times out becomes less likely. I believe it’s better to fail fast and let the requester retry later then to let capacity take a dive.&lt;/p&gt;
&lt;p&gt;One thing we used to do at LivingSocial during challenging days was to decrease the average time of a request by shutting down expensive operations. Some features had a “feature flag” around them (an if statement) and we could turn off all expensive operations in the critical path to make sure our capacity didn’t drop. This sounds really simple, and that is because it is.&lt;/p&gt;
&lt;h3 id=&#34;take-aways&#34;&gt;Take aways&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;First:&lt;/strong&gt; always be fast. Spending less time on each work item means it’s easier to maintain capacity. Granular work is easier to scale than monolithic work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second:&lt;/strong&gt; understand the queues in your system as much as possible. Where can things back up? What are the limits on those queues? Are they unbounded in size? Are there timeouts on the work items? If a work item times out while it’s in a queue, is it removed from the queue or does it persist? Go looking for settings, limits, etc of everything and compile a list of possible capacity killers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Third:&lt;/strong&gt; collect timing metrics everywhere. Everything is worth measuring. Sometimes you’ll learn that writing to your logs on &lt;code&gt;stdout&lt;/code&gt; is slowing down your request processing because someone accidentally changed &lt;code&gt;syslog&lt;/code&gt; to use synchronous &lt;code&gt;tcp&lt;/code&gt; and not &lt;code&gt;udp&lt;/code&gt;. It would be easy to think “I don’t need to know how long it takes to write a line of text to the log stream,” but yeah you might need to know that someday.&lt;/p&gt;
&lt;p&gt;Have fun. Build cool stuff. Don’t back up if you can help it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Any feedback? Did I make a mistake? Am I wrong on the internet?&lt;/em&gt; &lt;a href=&#34;mailto:me@nathanherald.com&#34;&gt;&lt;em&gt;Send me an email&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>ElixirConf EU 2017 in Barcelona</title>
      <link>https://nathanherald.com/posts/legacy/elixirconf-eu-2017-in-barcelona/</link>
      <pubDate>Tue, 27 Jun 2017 10:01:17 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/elixirconf-eu-2017-in-barcelona/</guid>
      <description>&lt;p&gt;Hello.&lt;/p&gt;
&lt;p&gt;I design distributed applications from UI to data storage as a career. I started out as a graphic designer, got into programming, and now that has spiraled out of control.&lt;/p&gt;
&lt;p&gt;Aside: I built my own presentation software using &lt;a href=&#34;https://choo.io&#34;&gt;choo&lt;/a&gt; and phoenix. If you want to have a lot more anxiety than necessary, then build your own presentation software. As an example: I don’t have speaker notes because I didn’t have time to implement that feature.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Screenshot of the presenter view for the presentation software I made for myself&#34;
    width=&#34;639&#34;
    height=&#34;435&#34;
    src=&#34;https://nathanherald.com/posts/legacy/elixirconf-eu-2017-in-barcelona/1-3Ibv-qH5Vo9-Fm8sjGC30A_hu7680339945200642797.png&#34;&gt;
  &lt;figcaption&gt;
    This is what the presentation software looks like for me.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;distributed-applications-arehard&#34;&gt;Distributed applications are hard&lt;/h3&gt;
&lt;p&gt;One has to deal with network partitions, concurrency, locking and/or linearization, stale data, etc. Here is a painting that represents how I feel about distributed systems: they are great fun and I feel dead.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Painting of a skeleton surfing&#34;
    width=&#34;800&#34;
    height=&#34;800&#34;
    src=&#34;https://nathanherald.com/posts/legacy/elixirconf-eu-2017-in-barcelona/1-aqjDRpRjbuT3rcWb1K1Pew_hu1380437794767337624.jpeg&#34;&gt;
  &lt;figcaption&gt;
    By Ted Parker — &lt;a href=&#34;http://ted-parker.com/portfolio/surfs-dead/&#34;&gt;&lt;em&gt;http://ted-parker.com/portfolio/surfs-dead/&lt;/em&gt;&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;network-latency&#34;&gt;Network latency&lt;/h4&gt;
&lt;p&gt;Network latency can cause stale reads from consistent systems. A server’s response for one request can arrive much later than expected, possibly after one or more other responses that were sent after it. This happens all the time. 😱&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Illustration of network request ordering&#34;
    width=&#34;800&#34;
    height=&#34;450&#34;
    src=&#34;https://nathanherald.com/posts/legacy/elixirconf-eu-2017-in-barcelona/1-S40jbGVy9x1Cnxq4csEXuA_hu13262106131644868555.png&#34;&gt;
  &lt;figcaption&gt;
    Networks can cause everything to be out of order, no matter which is sent first.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;scaling-ishard&#34;&gt;Scaling is hard&lt;/h4&gt;
&lt;p&gt;One has to deal with load balancing, parallelism, databases, AUTOVACUUM &amp;amp; VACUUM… Even after all these years managing databases is still hard.&lt;/p&gt;
&lt;h3 id=&#34;why-build-a-distributed-system-in-the-firstplace&#34;&gt;Why build a distributed system in the first place?&lt;/h3&gt;
&lt;p&gt;Small systems are super fun. I’ve really enjoying building systems for 100 or 1000 users in the past. But to support “billions” of users one has to get good at horizontal scaling, offline support, and dealing with bad networks. This means one must build a distributed system. 💔&lt;/p&gt;
&lt;h3 id=&#34;granularity&#34;&gt;Granularity&lt;/h3&gt;
&lt;p&gt;I recently watched &lt;a href=&#34;https://youtu.be/bo5WL5IQAd0&#34;&gt;a talk&lt;/a&gt; by &lt;a href=&#34;https://en.wikipedia.org/wiki/Joe_Armstrong_%28programming%29&#34;&gt;Joe Armstrong&lt;/a&gt; where he talks about the best ways to build concurrent software. His primary anaolgy is that: if your CPUs or VMs are like buckets or containers and you design your system components like large stones or bowling balls, then you end up with a lot of empty space in the containers. If you instead designed your components like small grains of sand, then you can really fill up each container to the brim with little wasted space.&lt;/p&gt;
&lt;p&gt;This really had an impact on me: &lt;strong&gt;granular = scalable.&lt;/strong&gt; 💝&lt;/p&gt;
&lt;h3 id=&#34;a-crazyidea&#34;&gt;A crazy idea&lt;/h3&gt;
&lt;p&gt;For a while I and some collegues have had this crazy idea: what if every user had their own database, maybe as a simple file stored on S3?&lt;/p&gt;
&lt;p&gt;Having a central database in a system makes a lot of things easier; however, a central database is hard to scale. What if we took this idea of granularity and really went with it?&lt;/p&gt;
&lt;h4 id=&#34;first-we-must-talk-abouttime&#34;&gt;First, we must talk about “time”&lt;/h4&gt;
&lt;p&gt;Human time cannot be trusted in a distributed system. Instead, we have to invent our own measurement for when our system “moves foward.” &lt;em&gt;Any working distributed system is an implementation of how that organization has understood time.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most systems I’ve worked with have used a central database to control time.&lt;/strong&gt; We use transactions to linearize updates to the important parts of our system. This means all our components can be relatively stateless and provide no guaruntees about how time works: we just let the database decide. If we remove the central database, then we need to have another way to control time.&lt;/p&gt;
&lt;h4 id=&#34;actors&#34;&gt;Actors&lt;/h4&gt;
&lt;p&gt;When building Wunderlist’s real-time backend we used actors to wrap each websocket connection. They are a great abstraction to contain state and linearize access to that state.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of a girl with a fire extinguisher&#34;
    width=&#34;1024&#34;
    height=&#34;837&#34;
    src=&#34;https://nathanherald.com/posts/legacy/elixirconf-eu-2017-in-barcelona/1-5HvA-QDfOWgDYSHmewejFA_hu17972822538574113958.jpeg&#34;&gt;
  &lt;figcaption&gt;
    Acting like a Firefighter &lt;a href=&#34;https://flic.kr/p/dnt7vz&#34;&gt;https://flic.kr/p/dnt7vz&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;elixir-processes&#34;&gt;Elixir processes&lt;/h4&gt;
&lt;p&gt;Processess are also a great way to linearize access to state. &lt;strong&gt;What if every user had their own process?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Elixir processes are addressable, even across machines, by a &lt;code&gt;pid&lt;/code&gt;. To trust a process as the authority for the state of a user we need to know there will only ever be one process for that user. &lt;em&gt;If we were to accidentally have two “myobie” processes, then we have a split brain.&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&#34;global&#34;&gt;&lt;strong&gt;:global&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Erlang ships with a &lt;code&gt;:global&lt;/code&gt; process registry which is shared across all connected nodes. However, we cannot rely on it to be constistent. You may be thinking &lt;em&gt;are you sure?&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If any name clashes are discovered, function Resolve is called.&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;&lt;a href=&#34;http://erlang.org/doc/man/global.html&#34;&gt;&lt;em&gt;http://erlang.org/doc/man/global.html&lt;/em&gt;&lt;/a&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are three words that jump out at me from that quote: clashes, discovered, and Resolve. It’s reasonable to assume that if it weren’t possible to have duplicates, then there would be no need to have a Resolve function.&lt;/p&gt;
&lt;p&gt;😐&lt;/p&gt;
&lt;h4 id=&#34;pg2&#34;&gt;&lt;strong&gt;:pg2&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;A popular erlang library is “process groups 2.” This library does not attempt to provide a consistent view of the cluster’s processes. &lt;em&gt;Are you sure?&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;pg2 replicates all name lookup information in a way that doesn’t require consistency …&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;&lt;a href=&#34;http://erlang.org/pipermail/erlang-questions/2012-June/067220.html&#34;&gt;&lt;em&gt;http://erlang.org/pipermail/erlang-questions/2012-June/067220.html&lt;/em&gt;&lt;/a&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This could be very useful for different types of applications where doing something twice wouldn’t hurt (like &lt;a href=&#34;https://github.com/phoenixframework/phoenix_pubsub/blob/master/lib/phoenix/pubsub/pg2.ex&#34;&gt;phoenix pubsub&lt;/a&gt;), but for my use case I really need to &lt;strong&gt;know&lt;/strong&gt; there are zero or one of something at all times.&lt;/p&gt;
&lt;p&gt;😐&lt;/p&gt;
&lt;h4 id=&#34;grpoc&#34;&gt;:grpoc&lt;/h4&gt;
&lt;p&gt;Same. &lt;em&gt;Are you sure?&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;While gproc has been tested very thoroughly … its reliance on gen_leader is problematic.&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;&lt;a href=&#34;https://christophermeiklejohn.com/erlang/2013/06/05/erlang-gproc-failure-semantics.html&#34;&gt;&lt;em&gt;https://christophermeiklejohn.com/erlang/2013/06/05/erlang-gproc-failure-semantics.html&lt;/em&gt;&lt;/a&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Are there problems with &lt;code&gt;gen_leader&lt;/code&gt;? I don’t know. I’ve read multiple articles saying something negative about &lt;code&gt;gen_leader&lt;/code&gt;, but I’ve never understood why. I intend to use &lt;code&gt;gen_leader&lt;/code&gt; sometime in a project and try to get more familiar with it. I don’t want to jump into that today.&lt;/p&gt;
&lt;p&gt;😐&lt;/p&gt;
&lt;h4 id=&#34;elixirregistry&#34;&gt;Elixir.Registry&lt;/h4&gt;
&lt;p&gt;Elixir ships with a &lt;code&gt;Registry&lt;/code&gt; as of version 1.4. We are saved!&lt;/p&gt;
&lt;p&gt;This registry is local to a single VM and it is consistent. It’s backed by ETS so it’s super fast. It’s &lt;em&gt;easy&lt;/em&gt; to be consistent on one machine. 😏&lt;/p&gt;
&lt;p&gt;What happens if that one VM stops? Our entire registry disappears and we cannot get any work done until it comes back. 😐 This could be fine if we didn’t mind the downtime, but I kinda do mind.&lt;/p&gt;
&lt;p&gt;How do we make 100% certain we never accidentally boot two vms? It may sound like an easy problem, but I’ll tell you it’s not. At Wunderlist we have an email related thing that we can only run one of, but I know we’ve accidentally run two before.&lt;/p&gt;
&lt;p&gt;☹&lt;/p&gt;
&lt;h4 id=&#34;zookeeper&#34;&gt;Zookeeper&lt;/h4&gt;
&lt;p&gt;What are we to do? &lt;strong&gt;Let’s outsource the problem to someone else.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Zookeper is consistent, pretty available, and partition tolerant. And, more importantly, &lt;strong&gt;it includes recipes&lt;/strong&gt;. Yes, it has instructions for common use cases including in it’s official documentation!&lt;/p&gt;
&lt;p&gt;➡️ &lt;a href=&#34;https://zookeeper.apache.org/doc/current/recipes.html&#34; title=&#34;https://zookeeper.apache.org/doc/current/recipes.html&#34;&gt;&lt;strong&gt;ZooKeeper Recipes and Solutions&lt;/strong&gt; &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And one recipe is super interesting: &lt;strong&gt;distributed locks.&lt;/strong&gt; 🔑&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of DJ Khalid&#34;
    width=&#34;740&#34;
    height=&#34;690&#34;
    src=&#34;https://nathanherald.com/posts/legacy/elixirconf-eu-2017-in-barcelona/1-ZdZGgshjhJcdEGlx4P8l6Q_hu6090087899466700549.png&#34;&gt;
  &lt;figcaption&gt;
    “Distributed locks are a major key.” [&lt;em&gt;https://www.flickr.com/photos/88009602@N05/8696887207&lt;/em&gt;](&lt;a href=&#34;https://www.flickr.com/photos/&#34;&gt;https://www.flickr.com/photos/&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Zookeeper is a battle tested system for getting time under control. We can know if one thing happens before another. That is really helpful.&lt;/p&gt;
&lt;h4 id=&#34;highlander&#34;&gt;Highlander&lt;/h4&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Photo of the main character from the TV show Highlander&#34;
    width=&#34;530&#34;
    height=&#34;784&#34;
    src=&#34;https://nathanherald.com/posts/legacy/elixirconf-eu-2017-in-barcelona/1-VRS7aSZ-QPVkWUE7hzrcLg_hu3381166808940348727.png&#34;&gt;
  &lt;figcaption&gt;
    Duncan MacLeod of the clan MacLeod.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I have created a library named after a 90’s TV show.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/myobie/highlander&#34;&gt;&lt;em&gt;https://github.com/myobie/highlander&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There can be only one.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It has a concept of “object” which are simple &lt;code&gt;GenServer&lt;/code&gt;s with some extra abilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Objects persist their state to S3 during a state update&lt;/li&gt;
&lt;li&gt;Objects read their state from S3 during &lt;code&gt;init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Objects respond to calls directly from their in-memory state&lt;/li&gt;
&lt;li&gt;Objects teardown after some period of inactivity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Highlander also includes a process registry which stores it’s information in Zookeper. Every process has a key named after it’s id and it’s value is the node name it’s running on. This means we can lookup where any process is running and send it messages from any node in the cluster.&lt;/p&gt;
&lt;h4 id=&#34;genservervia&#34;&gt;GenServer :via&lt;/h4&gt;
&lt;p&gt;There is this &lt;code&gt;:via&lt;/code&gt; concept with GenServers: they can have a fancy name that is a tuple instead of just an atom.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nc&#34;&gt;GenServer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:via&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;MyRegistry&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:user&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;user_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;ss&#34;&gt;:do_stuff&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What does a process registry look like in elixir? It implements four methods:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;defmodule&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;MyRegistry&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;send&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;whereis_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;register_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;unregister_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;How do we find a process through zookeeper?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;defmodule&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;MyRegistry&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;whereis_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ZK&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get_node_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;c1&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Simply query zookeeper for the key (node) with the same name as the process. It’s value will be the node name. From there we can construct a pid.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Registering a new process is similar:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;defmodule&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;MyRegistry&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;register_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ZK&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;create_znode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;c1&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Registering means trying to create a key (node) with the same name as the process.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In my &lt;code&gt;ZK&lt;/code&gt; module’s &lt;code&gt;create_znode&lt;/code&gt; if we are not the first to create the new key in zookeeper, then it will fail accordingly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;defmodule&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ZK&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;create_znode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ZNode&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;start_link&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ZNode&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;first?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;ss&#34;&gt;:ok&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ZNode&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;delete&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:already_exists&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;How do we know we were first? Zookeeper’s distributed lock recipe shows how to determine that. Here is how it’s implemented in Highlander:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;defmodule&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ZNode&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;init&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nc&#34;&gt;Zookeeper.Client&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:zk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;prefix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;UUID&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;uuid4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:hex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)),&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;to_string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;Node&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;ss&#34;&gt;makepath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;ss&#34;&gt;create_mode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:ephemeral_sequential&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;%{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;The most important part of this code snippet is&lt;/strong&gt; &lt;code&gt;**ephemeral_sequential**&lt;/code&gt;&lt;strong&gt;.&lt;/strong&gt; It is “ephemeral” so it’s temporary and if we drop our connection to zookeeper all the keys we created will be auto-deleted. It’s “sequential” so that zookeeper will append a number to the keyname we provided (the return value of &lt;code&gt;prefix/2&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zookeeper keeps time in order.&lt;/strong&gt; It’s very good and forming consensus around sequential numbers. So instead of asking everyone in it’s cluster “is this key already taken” it gets agreement on the next number, you create that key, then you look back at all keys that start with the same “prefix” and if you were the lowest number then you must have been first.&lt;/p&gt;
&lt;p&gt;Because of the “ephemeral” flag, we need to make sure that if we drop our zookeeper connection that we also &lt;code&gt;exit&lt;/code&gt; any highlander processes that we had booted — since their keys are no longer set in zookeeper at that point. We can do this with a supervision tree:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;opts&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;stop_on_disconnect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:zk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;children&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;worker&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;Zookeeper.Client&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;opts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;worker&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;Registry.Server&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[],&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;worker&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;Registry.NodeCycleServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[],&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;supervisor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;Object.Supervisor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[],&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;supervise&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;children&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;strategy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:rest_for_one&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;:rest_for_one&lt;/code&gt; strategy means that if any child dies, then all children declared after it in the array will be &lt;code&gt;exit&lt;/code&gt;&amp;lsquo;ed. So we simply put &lt;code&gt;Zookeeper.Client&lt;/code&gt; first there. That process will exit if it loses it’s connection because above we set &lt;code&gt;stop_on_disconnect&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;. It exiting will cause the registry server, my “node cycle server”, and all objects being supervised to also exit. Once zookeeper reconnects then the registry and object supervisor will restart and we are back in business.&lt;/p&gt;
&lt;h3 id=&#34;developer-ux&#34;&gt;Developer UX&lt;/h3&gt;
&lt;p&gt;Highlander “objects” are &lt;code&gt;GenServer&lt;/code&gt;s with some extra functions. Instead of having to build the same &lt;code&gt;GenServer&lt;/code&gt;s over and over again, I also created some macros to help with that. An example of making a “todo” object to contain a simple task:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;defmodule&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Todo&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kn&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Highlander.Object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:todo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;defobject&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;completed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;color&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:blue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;defobject&lt;/code&gt; really just calls &lt;code&gt;defstruct&lt;/code&gt; behind the scenes and setups up seriliazation for when the todo’s state is persisted to S3. Yes, with this “object,” every todo will be it’s own process and it’s state will be serialized to S3. Here is an example of how to use it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;UUID&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;uuid4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;todo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Todo&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;assert&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;todo&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;completed&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;assert&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;todo&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;title&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;assert&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;todo&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;color&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:blue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;todo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;%{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;todo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Todo&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;put&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;todo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# any amount of time later&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;todo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Todo&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;assert&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;todo&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;title&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;model-everything-as-aprocess&#34;&gt;Model everything as a process&lt;/h4&gt;
&lt;p&gt;With this approach we can model everything in our system as it’s own process. That makes it super easy to serialize access to state, persist it to S3 as necessary, and our system becomes a reflection of the active “objects” currently collaborating.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;defmodule&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;User&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kn&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Highlander.Object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:user&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;defobject&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;An example user module.&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&#34;model-all-shared-objects-as-their-ownprocess&#34;&gt;Model all shared objects as their own process&lt;/h4&gt;
&lt;p&gt;After every user has a process with state, it is tempting to nest everything under the user who created it. For a lot of data this makes perfect sense. But objects which are collaborated on by multiple users needs to be separated out, given it’s own state, and possibly protected with some sort of permissions.&lt;/p&gt;
&lt;p&gt;An example might be a shared list, where any user with permission can manage a list of todos:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;defmodule&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;List&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kn&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Highlander.Object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:list&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;defobject&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;todos&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;defmodule&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Todo&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;defstruct&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;completed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;color&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:blue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To manage permission, the &lt;code&gt;User&lt;/code&gt; probably needs to know which lists it has seen before. And the &lt;code&gt;List&lt;/code&gt; could keep an array of allowed users. See below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;defmodule&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;User&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kn&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Highlander.Object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:user&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;defobject&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;known_lists&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;defmodule&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;List&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kn&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Highlander.Object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:list&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;defobject&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;todos&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[],&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;allowed_users&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;handle_update&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:insert_todo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;todo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Enum&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;any?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;allowed_users&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;fn&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;id&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;id&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;%{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;todos&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;todo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;todos&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So each list is a concurrent object in the system. If one list because super busy with updates, it wouldn’t slow down any other list’s access or anything like that. The &lt;code&gt;handle_update&lt;/code&gt; above doesn’t actually work yet: I haven’t decided on the syntax for handling state updates yet. &lt;strong&gt;Do you have opinions or ideas?&lt;/strong&gt; Let me know.&lt;/p&gt;
&lt;h3 id=&#34;an-aside-multi-vmtesting&#34;&gt;An aside: Multi-VM testing&lt;/h3&gt;
&lt;p&gt;When building highlander I needed to boot multiple erlang nodes to be able to test cross-node calls and stuff like that. That wasn’t entirely easy or straight forward. It turns out &lt;a href=&#34;https://github.com/phoenixframework/phoenix_pubsub/tree/master/test/support&#34;&gt;Phoenix does this&lt;/a&gt; to test it’s websockets stuff, so I just copied a lot of that code.&lt;/p&gt;
&lt;p&gt;The most interesting bits are:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-elixir&#34; data-lang=&#34;elixir&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# how to boot a new VM process&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;spawn&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;ss&#34;&gt;:net_kernel&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;start&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:&amp;#34;primary@127.0.0.1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;ss&#34;&gt;:erl_boot_server&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;start&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;allow_boot&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;to_char_list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;127.0.0.1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nc&#34;&gt;Application&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get_env&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:highlander&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;:spawn_nodes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Enum&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;async&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;fn&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;spawn_node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ni&#34;&gt;&amp;amp;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Enum&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;await&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ni&#34;&gt;&amp;amp;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;30_000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# how to start a `:slave` and then send it the code to load&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;defp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;spawn_node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;node_host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:slave&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;start&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;to_char_list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;127.0.0.1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;node_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;node_host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;inet_loader_args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;add_code_paths&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;transfer_configuration&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;ensure_applications_started&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can see all of it in the &lt;a href=&#34;https://github.com/myobie/highlander/tree/master/test/support&#34;&gt;highlander repo’s test folder&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;The skeleton surfing again, but zoomed in&#34;
    width=&#34;800&#34;
    height=&#34;450&#34;
    src=&#34;https://nathanherald.com/posts/legacy/elixirconf-eu-2017-in-barcelona/1-FbxEeSZG0ulVSr0BYUBYYQ_hu15374176244318222221.png&#34;&gt;
  &lt;figcaption&gt;
    That was fun!
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;scaling&#34;&gt;Scaling&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Granular = scalable.&lt;/strong&gt; Having all these small processes is a lot like “pouring sand” into our cluster.&lt;/p&gt;
&lt;p&gt;H&lt;strong&gt;ow many locks can a small zookeeper cluster handle?&lt;/strong&gt; I don’t know.&lt;/p&gt;
&lt;p&gt;If it became a problem one could shard zookeeper. Or one could do what other libraries do and not store a zookeeper key for every process, but instead setup a partition map in zookeeper and use that to shard processes onto the box they map to. Then there would only be a key for every erlang VM and not for every erlang process.&lt;/p&gt;
&lt;h4 id=&#34;orleans&#34;&gt;Orleans&lt;/h4&gt;
&lt;p&gt;That is what &lt;a href=&#34;https://github.com/dotnet/orleans&#34;&gt;orleans&lt;/a&gt; does: setup a partition map in zookeeper. Orleans is what the Halo team used to scale Halo 4 and 5. As far as I know every player, game, etc is a process in the cluster and they collaborate with message passing.&lt;/p&gt;
&lt;p&gt;There is also Microsoft Service Fabric which is a lot like Orleans, but tries to help with building stateful and stateless services more than actors. It also uses zookeeper to setup a partition map for it’s services. It also persists the services state across the cluster when that state is updated.&lt;/p&gt;
&lt;h4 id=&#34;akkahttpakkaio&#34;&gt;&lt;a href=&#34;http://akka.io&#34;&gt;Akka&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Sure. That’s a good choice. I am not really doing any scala right now and I’m a bit scala’ed out for the moment. As I said before, Wunderlist’s websockets servers are all in scala (using &lt;a href=&#34;https://playframework.com&#34;&gt;play&lt;/a&gt;) and it’s very reliable and performant. Love it. It’s good. Use it.&lt;/p&gt;
&lt;h4 id=&#34;riak_core&#34;&gt;riak_core&lt;/h4&gt;
&lt;p&gt;Sure, you could use the riak internals to do your own consensus, but I haven’t yet drank enough coffee in my life to start down that road. Maybe I will one day, and if you are interested then definitely go for it, but I am not able to think about that without having a lot of anxiety. 🤷‍♂️ 🌳 🤷‍♀️&lt;/p&gt;
&lt;p&gt;And that’s it. Hope you maybe learned something.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Have fun. Have crazy ideas. Build things.&lt;/strong&gt; 🏁&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>A way to procrastinate</title>
      <link>https://nathanherald.com/posts/legacy/a-way-to-procrastinate/</link>
      <pubDate>Thu, 22 Jun 2017 23:53:43 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/a-way-to-procrastinate/</guid>
      <description>&lt;p&gt;Wasting time is something I fight against all the time. I mean &lt;em&gt;waste&lt;/em&gt; in the sense of &lt;em&gt;having zero value&lt;/em&gt;. However, I also know that &lt;strong&gt;I cannot be productive every hour of every day.&lt;/strong&gt; I’ve tried and it’s too exhausting.&lt;/p&gt;
&lt;p&gt;Instead, I am always doing around four different side-projects, reading a couple books, watching a few lectures, etc. Having multiple things going on doesn’t mean you lack focus; &lt;strong&gt;focus is being able to prioritize and choose the correct task for right now.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;1-1jcC1GaiZe8xkklerk78DA.jpeg&#34; alt=&#34;Wasting vs. investing time.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;dont-wast-time-investit&#34;&gt;Don’t wast time, invest it.&lt;/h3&gt;
&lt;p&gt;Procrastination is about priorities. Is your current task what you really need to be doing? Is your due-date real or just a trick to motivate yourself? What happens if you don’t finish on time?&lt;/p&gt;
&lt;p&gt;I believe we can, and should, invest in ourselves a little everyday if possible. Instead of constantly working, we can learn, read, and rest. Then we can work more effectively when things are pressing and due-dates are real. Sometimes things really do need to get done, and fast, but I believe a lot of people are &lt;em&gt;busy being busy&lt;/em&gt; and not because things are truly pressing.&lt;/p&gt;
&lt;h4 id=&#34;waste&#34;&gt;Waste&lt;/h4&gt;
&lt;p&gt;My versions of wasting time are: scrolling twitter for more than 5 minutes, clicking on random youtube videos for no reason, reading political news, watching some old show on netflix…&lt;/p&gt;
&lt;p&gt;These things almost never bring me happiness, they are never valuable for me, and I know I’m wasting the time I spend on them. These are the things that consume my time wastefully, yours are almost certain different. I think it pays to know them so you can be more mindful. Being mindful is a super power that will allow you to procrastinate more effectively.&lt;/p&gt;
&lt;h4 id=&#34;invest&#34;&gt;Invest&lt;/h4&gt;
&lt;p&gt;Instead I try to: read a book, take a nap, build something useful, learn more about philosophy, science, or culture… I’ve been trying to get into making music, writing more about myself and my work, and reading. I was never a reader growing up and I’m trying to make up for that time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;1-U6MO7j0RxDjrqSd0Pxo96A.jpeg&#34; alt=&#34;An image to represent “making music.” Do something recharging regularly instead of work, if you can.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Some days the most important decision I make is to wait, rest, and then do work. Instead of always working during normal business hours, I sometimes take the time earlier in the day to do other things and then focus on work in the evening after letting the problems bake a bit. I might spend that day building a new webapp to solve some niche problem I have, design a new logo or a set of icons for an app I want to build, or watch lectures about databases, design, or the history of hip-hop. I consider all of these eventually valuable.&lt;/p&gt;
&lt;h4 id=&#34;being-late&#34;&gt;Being late&lt;/h4&gt;
&lt;p&gt;I try not to let people down. I try not to be late on things. But sometimes I end up in a place where I just cannot work more hours, I can’t build more things, I cannot create more stuff without reading, sleeping, or somehow recharging. I’ve overworked myself many times in the past and I’m just not into that anymore. It’s always made things worse.&lt;/p&gt;
&lt;p&gt;Let the stakeholders know what’s up, let them know you didn’t plan accordingly, and learn for next time. I’d rather be honest, realistic, and eventually get better at planning.&lt;/p&gt;
&lt;h4 id=&#34;it-costs-something&#34;&gt;It costs something.&lt;/h4&gt;
&lt;p&gt;I’ve found that it costs a whole lot to construct a less stressful environment where I can do those things which recharge me. &lt;em&gt;How can I take time to read a book when I have looming deadlines and I might get fired? When am I going to find the time to hang out with friends, I’ve got all this work to do? I don’t have time to take a nap, I gotta have this report turned in by midnight.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Yes.&lt;/p&gt;
&lt;p&gt;There is a cost to prioritizing yourself. Saying “no” always costs something. Instead of imaging it’s always impossible, I recommend working out the realistic costs of these decisions so you can do a cost/benefit analysis for yourself. Don’t work from incomplete assumptions if you don’t have to. Sometimes it does cost too much, but make sure you know that.&lt;/p&gt;
&lt;h3 id=&#34;it-pays-off-atwork&#34;&gt;It pays off at work.&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;(At least for me.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I’ve almost never done something at work for the first time. Instead, I’ve experimented on my own project or built/designed something for myself, then repeated that at work. Even working on the Wunderlist rewrite I ended up just re-typing tons of code that I had typed before, having conversations that I’d already debated with myself, and mostly just implementing ideas that I’d previously worked out.&lt;/p&gt;
&lt;p&gt;I like making my mistakes early and in a controlled environment when possible. It’s not always possible: sometimes I gotta do something new right now. Hopefully I’ve invested in myself so I’m rested, aware of what’s possible, what’s easy, and can break the problem down into small enough chunks to attack. When that’s not the case it takes me far longer to be effective at my job.&lt;/p&gt;
&lt;p&gt;Reading regularly has been the best decision I’ve made for myself in years, probably ever. Not just because we absorb information from books differently than other mediums, but also because I’ve got a really good excuse to procrastinate when I need it. It’s an enjoyable privilege to be able to take time to read through a book. There have been periods in the past when I didn’t have the time (or the money to make the time) to read and I’m very grateful that I can currently carve that time out for myself.&lt;/p&gt;
&lt;p&gt;Good luck. Do your best. And, if you don’t already prioritize it, hopefully you can find some time to invest in yourself soon.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;All the photos in this post are from&lt;/em&gt; &lt;a href=&#34;https://deathtothestockphoto.com&#34;&gt;&lt;em&gt;deathtothestockphoto.com&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

        
      </description>
    </item>
    <item>
      <title>Swift already has a Result type</title>
      <link>https://nathanherald.com/posts/legacy/swift-already-has-a-result-type/</link>
      <pubDate>Mon, 23 Jan 2017 11:13:32 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/swift-already-has-a-result-type/</guid>
      <description>&lt;figure&gt;
  &lt;a href=&#34;https://unsplash.com/@jdsimcoe&#34;&gt;
  &lt;img
    alt=&#34;Photo of a poster reading &amp;#39;Ask more questions&amp;#39;&#34;
    width=&#34;1200&#34;
    height=&#34;1200&#34;
    src=&#34;https://nathanherald.com/posts/legacy/swift-already-has-a-result-type/1-X6Bjvc5hU9Z0PaTeQMpNGQ_hu3002587424486339008.jpeg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Photo by &lt;a href=&#34;https://medium.com/u/a833450b230&#34;&gt;JD Simcoe&lt;/a&gt; @ &lt;a href=&#34;https://unsplash.com/@jdsimcoe&#34;&gt;https://unsplash.com/@jdsimcoe&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Over the last few years I’ve had the privilege of writing and deploying applications using a myriad of languages. Scala, ruby, javascript, haskell, clojure, etc. Every time I pick up a tool, especially if it’s new to me, I try to learn &lt;a href=&#34;http://frankchimero.com/writing/the-webs-grain/&#34;&gt;the grain of it&lt;/a&gt;. A programming language is a toolbox of ideas and concepts that anyone can use to build stuff. Yet, I see a lot of dissatisfaction from my colleagues and peeps online with the tools currently available.&lt;/p&gt;
&lt;p&gt;There is a great talk about how &lt;a href=&#34;https://realm.io/news/tryswift-rob-napier-swift-legacy-functional-programming/&#34;&gt;swift is not a functional language&lt;/a&gt; by Rob Napier and you should watch it. It’s a great lesson about how one should use a language how it’s made and not how one wishes it were. However, I was really disappointed that Rob made the case that swift should add a &lt;code&gt;Result&lt;/code&gt; enum. Swift already has this feature, but I guess a lot of people don’t like it.&lt;/p&gt;
&lt;p&gt;Swift has an &lt;code&gt;Optional&lt;/code&gt; type, but you don’t normally see it directly. You interact with optionals through language constructs. An example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Person&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;bio&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;person&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Person&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Nathan&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;bio&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;person&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bio&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;bp&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;\(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;person&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;’s bio: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;\(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bio&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;bp&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;\(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;person&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; doesn&amp;#39;t have a bio.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The words optional, some, and none are nowhere to be found in the above example.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Optional&lt;/code&gt; is basically a “maybe” type and it’s really nice to have support directly in the language for it. Instead of writing a bunch of &lt;code&gt;case .some&lt;/code&gt; and other similar statements, we can just use &lt;code&gt;let&lt;/code&gt; and other language constructs to work through detecting if anything is in there.&lt;/p&gt;
&lt;p&gt;This example below is similar to a lot of examples I’ve seen for a &lt;code&gt;Result&lt;/code&gt; type:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;enum&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;T&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;success&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;T&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;isLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;_&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;number&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;50&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;success&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Sorry.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;isLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;70&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// error(&amp;#34;Sorry.&amp;#34;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;isLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// success(20)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At first glance, this seems pretty nice. I’ve added enums like this to a lot of my swift projects. However, recently I’ve noticed that I always end up having to perform a lot of ceremony to extract the values from the enum:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;isLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;40&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;switch&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;success&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt; &lt;span class=&#34;bp&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Yes, &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;\(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; is in the lower half!&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt; &lt;span class=&#34;bp&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;\(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; Not in the lower half.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or one can try to only extract and use the success case:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;success&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;isLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;bp&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;OK, that worked, but it&amp;#39;s pretty weird.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a lot of work to fight against the tools that are already in the language. Swift already has a way of marking a function as returning one of two types, one is a success and the other is an &lt;code&gt;Error&lt;/code&gt;, and it’s &lt;code&gt;throws&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;NotLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Error&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;isLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;_&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Int&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;number&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;50&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;throw&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NotLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can use the built in facilities of the language to call the function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;try&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;isLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;70&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;try&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;isLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// 20&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;do&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;isLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;70&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;bp&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Yes, &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;\(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; is in the lower half!&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NotLowerHalf&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;bp&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Not in the lower half.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;isLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;bp&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;OK, that worked, and it&amp;#39;s not _too_ weird.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;isLowerHalf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;40&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// would cause a crash if it failed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Many people I’ve talked to say they avoid &lt;code&gt;throws&lt;/code&gt; because they believe using exceptions for control flow is bad. I agree, exceptions are not reasonable for control flow most of the time.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;throw&lt;/code&gt; does not raise an exception. From &lt;a href=&#34;https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html&#34;&gt;the swift docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Swift provides first-class support for throwing, catching, propagating, and manipulating recoverable errors at runtime.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Errors are not exceptional circumstances. Recoverable errors are expected and should be planned for.&lt;/p&gt;
&lt;p&gt;A function marked as &lt;code&gt;throws&lt;/code&gt; returns an unsuccessful result of type &lt;code&gt;Error&lt;/code&gt; and the compiler forces the programmer to handle both the success and error cases. &lt;code&gt;do&lt;/code&gt; and &lt;code&gt;catch&lt;/code&gt; are like specialized &lt;code&gt;switch&lt;/code&gt; and &lt;code&gt;case&lt;/code&gt; keywords for when a function might return a success or error value and offer the pattern matching features one expects.&lt;/p&gt;
&lt;p&gt;And yes, there is &lt;code&gt;rethrows&lt;/code&gt; to propagate an error up the function stack. I’ve done the exact same thing in scala to “slipstream” an error from a &lt;code&gt;Result&lt;/code&gt; up during things like a web request. This is control flow using types and pattern matching and I am very glad that it’s built into the language and not something I have to build on my own each time.&lt;/p&gt;
&lt;p&gt;The word &lt;code&gt;try&lt;/code&gt; is the perfect way to explain what I’m trying to accomplish: &lt;em&gt;try this and if it doesn’t work out then I’ll handle it this other way.&lt;/em&gt; &lt;code&gt;do&lt;/code&gt; isn’t my favorite word for the start of the try/catch situation, but I can’t think of anything better.&lt;/p&gt;
&lt;p&gt;I’m not a swift expert, I don’t use it everyday, and I’d like to hear what you think about this subject. Should swift add a &lt;code&gt;Result&lt;/code&gt; enum anyway and let the programmer choose between &lt;code&gt;throws&lt;/code&gt; and &lt;code&gt;Result&lt;/code&gt;? Or would that fragment libraries where some throw while others are all enums all the way?&lt;/p&gt;
&lt;p&gt;Thanks for reading.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>I’m getting “engagement”</title>
      <link>https://nathanherald.com/posts/legacy/im-getting-engagement/</link>
      <pubDate>Fri, 11 Nov 2016 10:48:45 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/im-getting-engagement/</guid>
      <description>&lt;p&gt;I’m not sure how, but my tweet got retweeted beyond my normal network of people and I am getting a lot of responses. The &lt;a href=&#34;https://twitter.com/i/moments/796417517157830656&#34;&gt;moment I tweeted&lt;/a&gt; has been tweeted by others I know, so I wonder if the same thing happened to them. I expected my network to be small and homogenous, so I am surprised by the reach this tweet has gotten. People really have an emotional response to reading through that moment and want to spread it or weigh in.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;A bar chart showing an increase in tweet engagement stating &amp;#39;Your Tweets earned 40,427 impressions over the last 24 hours&amp;#39;&#34;
    width=&#34;289&#34;
    height=&#34;238&#34;
    src=&#34;https://nathanherald.com/posts/legacy/im-getting-engagement/1-izD8mL4P47Cld4tQzttmLQ_hu15133719495319264158.png&#34;&gt;
&lt;/figure&gt;

&lt;p&gt;The way Twitter’s UI works for responding or retweeting is confusing and I think this is why people are responding to me so often. There also seem to be many conversations between lots of other people and I get notified of every response. I normally don’t get to see this side of Twitter since I don’t have a ton of followers. The total impressions for my tweet are 34,805. I’m not sure how that compares to others but my next largest number of impressions I can find is around 800. Somehow that adds up to &amp;gt;40,000 I guess.&lt;/p&gt;
&lt;p&gt;I’ve only blocked maybe 20 people because of racist or violent responses, so I think that is pretty low percentage to what I’ve heard from friends with more followers. I also had to block some people because I just kept getting more and more responses from them.&lt;/p&gt;
&lt;p&gt;Twitter is able to cause a flood of responses from all sides. I haven’t received much abuse, but I have friends who have. I’m very privileged because if this were my normal twitter experience, then I would leave. Others stick it out to connect with others, even through the torrents of responses like I’m temporarily seeing.&lt;/p&gt;
&lt;p&gt;Posting a link isn’t an endorsement or to shame, but people project onto it what they want. Some people assumed I was sympathetic to the victims of the hate speech while some others assumed I was sharing that link to gloat about how awesome it is to not need to be politically correct.&lt;/p&gt;
&lt;p&gt;Any software that connects people needs to have tools and protections in place for it’s users. Twitter is failing. It allows people to swarm others with any content they choose and rarely finds that content to be against it’s terms of service. If you build software (I do) then you need to think about your most vulnerable users and plan how you will enable and empower them to have a positive experience. Don’t be lazy, there are real consequences if you are.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>CloudKit</title>
      <link>https://nathanherald.com/posts/legacy/cloudkit/</link>
      <pubDate>Fri, 12 Aug 2016 17:45:48 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/cloudkit/</guid>
      <description>&lt;p&gt;The new capabilities in CloudKit for iOS 10 and macOS Sierra are super encouraging. I have been convinced for some time that collaboration and sharing are incredibly important for the future of all mobile productivity software. Productivity software is all about managing data, people, and how they relate to each other. The new additions from Apple make this easier for a larger number of people. As &lt;a href=&#34;http://www.nytimes.com/2016/08/08/technology/once-taunted-by-steve-jobs-companies-are-now-big-customers-of-apple.html?src=me&amp;amp;_r=0&#34;&gt;more enterprises buy iOS devices instead of Windows&lt;/a&gt;, this could be even more important sooner.&lt;/p&gt;
&lt;p&gt;When beginning to build the realtime sync for Wunderlist we tried to setup our server architecture as a product which the native applications “consumed.” We have had some success with this approach, but we never truly made a generic “realtime storage and collaboration system.” Which is fine, we make a to-do manager and not a storage service. (Of course we talk sometimes about how fun it would be to make our stack generic and open.)&lt;/p&gt;
&lt;p&gt;We knew we were going to build a system that will be obsolete very quickly. Just how people choose between databases (MySQL or Postgres), in the future people will choose between their serverless realtime storage and collaboration products (&lt;a href=&#34;https://en.wikipedia.org/wiki/CloudKit&#34;&gt;CloudKit&lt;/a&gt;, &lt;a href=&#34;http://hood.ie&#34;&gt;hoodie&lt;/a&gt;, &lt;a href=&#34;https://deepstream.io&#34;&gt;deepstream&lt;/a&gt;, …). &lt;em&gt;(We need a shorter name for serverless realtime storage and collaboration product. Also is it serverless or nobackend?)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Apple has been very, very slowly evolving iCloud over time and now they have built a file/blob storage system, a record/object storage system, sharing and collaboration (ACLs), and basic conflict management. They are succeeding at a huge scale and I’m sure it will eventually be “realtime.” &lt;em&gt;(Realtime means: very fast. Like a few seconds or less to get updates anywhere in the world.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Already, when using an app that stores data in iCloud Drive: I edit on my iMac, move over to the couch to edit on my MacBook, changes are already there. And later when I’m on the go I make some edits on my iPhone which all syncs together fine. I am collaborating with myself. The next obvious step was to collaborate with any number of people and that is just what is coming soon.&lt;/p&gt;
&lt;figure&gt;
  &lt;img
    alt=&#34;Notes’s new sharing screen&#34;
    width=&#34;311&#34;
    height=&#34;552&#34;
    src=&#34;https://nathanherald.com/posts/legacy/cloudkit/1-5_qp7e2QY6mjv1NfWP38Mw_hu7059793691416387993.png&#34;&gt;
  &lt;figcaption&gt;
    Notes’s new sharing screen
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Wunderlist’s realtime sync won’t be a competitive advantage for long. Apple’s Notes is a great example of how one can use the new APIs to take an existing product and make it collaborative. I expect in a few years this will be a common service provided by all operating system makers.&lt;/p&gt;
&lt;p&gt;While Apple does provide a javascript CloudKit SDK, and it is already being used on the iCloud web version of Notes to provide sharing and collaboration, they don’t offer native SDKs for Android or Windows. (&lt;a href=&#34;https://github.com/jaumecornado/DroidNubeKit&#34;&gt;Someone did port the web service calls to Android&lt;/a&gt;, which is amazing.)&lt;/p&gt;
&lt;p&gt;Some things you might want to look into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/CloudKit&#34;&gt;CloudKit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pouchdb.com&#34;&gt;pouchdb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://hood.ie&#34;&gt;hoodie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://deepstream.io&#34;&gt;deepstream&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://realm.io&#34;&gt;realm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Who is going to create the first good multi-platform serverless/nobackend realtime collaboration and storage system?&lt;/em&gt;&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Be.</title>
      <link>https://nathanherald.com/posts/legacy/be/</link>
      <pubDate>Thu, 19 May 2016 09:35:57 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/be/</guid>
      <description>&lt;p&gt;I learned at a young age to hold on to possessions, people, and desires with an open hand. Things come and go. Grasping tightly does not help. It only forces us to be anxious, stressed, or worse depressed. It is tempting to imagine that holding on less tightly is easier for someone with more money, more status, or more fame.&lt;/p&gt;
&lt;p&gt;It is not.&lt;/p&gt;
&lt;p&gt;It’s not possible to live with nothing. We are all pursuing something and that pursuit &lt;em&gt;can most likely&lt;/em&gt; be made easier with money.&lt;/p&gt;
&lt;p&gt;I’ve become a person who values quiet time, watching and reading stories, and drinking a warm beverage. But even more I value spending time talking to someone, not for gain or obligation, but to just be there with them. Being with someone is one of my most favorite things. It’s also something I think very few people really practice.&lt;/p&gt;
&lt;p&gt;The things that I value most are said to be free or nearly. But while a used book, some tea, and a sketchbook are close to free: the free time and lack of stress to enjoy them are not. The less anxious, the less hurried, and the more prepared seem to have more chances to partake in lovely things.&lt;/p&gt;
&lt;p&gt;People living in poverty are 10% or more less productive simply because of their predicament. The stress of having little makes it more difficult to climb up to have more. Other stresses have the same effect; I’ve experienced this. Still, grasping on to things tighter does not keep them around longer.&lt;/p&gt;
&lt;p&gt;Be with someone, be present, and let them know you are simply there. Hold on to them with an open hand. Maybe they’ll be a little less stressed and little more able to climb up somewhere.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>The Network Is Never There for You</title>
      <link>https://nathanherald.com/posts/legacy/the-network-is-never-there-for-you/</link>
      <pubDate>Fri, 26 Feb 2016 12:33:36 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/the-network-is-never-there-for-you/</guid>
      <description>&lt;p&gt;&lt;em&gt;This started as documention for some of the things that are hard about building a distributed system like Wunderlist. The goal is to clarify some assumptions about distributed systems and come up with possible solutions. Mostly: how can one persist data to a database while also notifying others of those changes in a reliable way. Real-time™ is hard.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Also, there is some pseudo code in this post which is basically javascript. Don’t comment on code style. If there are mistakes, then please let me know. But it’s not really javascript, so don’t worry about it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;1-pVZMTMxNNaoFDBdL2N32aA.jpeg&#34; alt=&#34;A broken road with a sign reading &amp;lsquo;computer error&amp;rsquo;&#34;&gt;&lt;/p&gt;
&lt;p&gt;It’s tempting to imagine that distributed systems are “simple” or “easy.” I’d like to describe a simple system and go through a few issues that turn out to be a bear. The goal here is to force to the front some basic assumptions one should have when building a distributed system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adjacent lines of code do not represent an atomic unit of work&lt;/li&gt;
&lt;li&gt;When network calls fail, it’s impossible to know what happened&lt;/li&gt;
&lt;li&gt;Transactions and mutexes are only useful inside one process on one computer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The simple system I want to build is this:&lt;/p&gt;
&lt;p&gt;A database (&lt;a href=&#34;http://www.postgresql.org&#34;&gt;postgres&lt;/a&gt;) will hold a value. The value can be updated atomically and the rest of the system wants to be notified about the change in a reliable way. Every change should be queued up so some other part of the system can read and process each one, possibly sending it to other parts of the system like a phone or something. These phones want to know about changes as fast as possible!&lt;/p&gt;
&lt;p&gt;This is actually a very complicated problem, so let’s size it down some at first: instead of notifying the system of every change, let’s just publish the latest value to &lt;a href=&#34;http://redis.io&#34;&gt;redis&lt;/a&gt;. A phone can poll frequently for the new value from redis. And let’s just increment a counter in the database. Much simpler.&lt;/p&gt;
&lt;p&gt;The value in redis should never be different from the value in the database. (Which is impossible, but still let’s walk through it and see. Also, if you disagree that it’s impossible, then please keep reading and don’t build anything until you’re done :)&lt;/p&gt;
&lt;p&gt;There are a lot of ways to increment a counter in postgres. The best ways are to either use a sequence (a built in feature of the database) or to issue a SQL statement similar to:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;UPDATE counters SET value = value + 1 where counter\_id = 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let’s assume we have a function updateCounter which performs that SQL and then a function &lt;code&gt;updateRedis&lt;/code&gt; which does a&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;SET counter:last 2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;where 2 is the return value from &lt;code&gt;updateCounter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So our code is now:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function receiveConnection() {  
    let latest = updateCounter()  
    updateRedis(latest)  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And this falls down in every way. Let’s go through the problems one by one.&lt;/p&gt;
&lt;h4 id=&#34;adjacent-lines-of-code-are-notatomic&#34;&gt;Adjacent Lines of Code Are Not Atomic&lt;/h4&gt;
&lt;p&gt;In our simple two line function, it’s entirely possible that &lt;code&gt;updateRedis&lt;/code&gt; never gets called. Here is an ASCII diagram of the places this function can fail:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function receiveConnection() {  
---&amp;gt;  
---&amp;gt;let latest = updateCounter()  
---&amp;gt;  
---&amp;gt;updateRedis(latest)  
---&amp;gt;  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While it’s easy to predict where execution of this program starts, it’s impossible to predict where it will end. A simple reason is your process could be killed at any moment. But there are other reasons: memory starvation, hardware failure, CPU load is too high, etc. It’s also impossible to predict how much time this program will spend at any one of the five marked points. There could be slow GC, scheduling issues, CPU load again, etc.&lt;/p&gt;
&lt;p&gt;The time it takes to get from updateCounter to &lt;code&gt;updateRedis&lt;/code&gt; is indeterminate and so our system is inconsistent for an indeterminate amount of time: it’s possible to read a value from redis that is old.&lt;/p&gt;
&lt;p&gt;It’s also possible that updateCounter succeeds, but &lt;code&gt;updateRedis&lt;/code&gt; fails or the program never even makes it that far.&lt;/p&gt;
&lt;p&gt;This simple program doesn’t solve our problem and isn’t a very good component in a distributed system.&lt;/p&gt;
&lt;p&gt;So let’s add some error checking:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function receiveConnection() {  
    var latest

    try {  
        latest = updateCounter()  
    } catch {  
        latest = rollbackCounter()  
    } finally {  
        try {  
            updateRedis(latest)  
        } catch {  
            rollbackCounter()  
        }  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This might at first seem great, but it’s actually worse. The &lt;code&gt;updateRedis&lt;/code&gt; call could succeed and yet appear to fail, so we would rollback the counter. Now the value in redis is greater than the value in the database.&lt;/p&gt;
&lt;p&gt;It’s just a futile effort to try to add enough error checking for a simple reason: the errors are mostly meaningless.&lt;/p&gt;
&lt;h4 id=&#34;when-networks-fail-its-impossible-to-knowwhy&#34;&gt;When Networks Fail, It’s Impossible to Know Why&lt;/h4&gt;
&lt;p&gt;Network timeouts, writing to the socket failure, reading from the socket failure, packet loss, and any other number of things are the types of failures where the caller has no idea what the callee did. Without a response to inspect we are stuck with Schrödinger’s request. The callee might have succeeded, but the network between the caller might have had a blip.&lt;/p&gt;
&lt;p&gt;No amount of rescuing or catching errors can fix this issue. The only real way to take care of this is to make the program (and the request to trigger it) idempotent. Either the program needs to operate in a truly atomic way or it needs to not matter if any one part fails.&lt;/p&gt;
&lt;p&gt;We are basically screwed now with what we have. Since the increment is not directly tied to the redis call, this function is not idempotent. Let’s try to make it work that way.&lt;/p&gt;
&lt;p&gt;One rule we could add is that the caller needs to let us know what its current view of the world is (the current counter’s value) so we can make sure it’s up to date. If you are not up to date, then you don’t get to make changes. It’s a simple rule, but I find it helps a ton in a lot of cases. I also stole this idea from couchdb.&lt;/p&gt;
&lt;p&gt;Our code can now look like:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function receiveConnection(remoteValue) {  
    var latest  
    let current = getCounter()  
    if (current == remoteValue) {  
        latest = updateCounter()  
        updateRedis(latest)  
    }  
    return latest  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Of course, we will have the issue with network failures hiding successful sets from us. This method is not fully idempotent, it just won’t increment the value twice until the caller is updated. But it does only update the counter once per remoteValue instead of over and over again. It would be nice to try to rollback the increment if something goes wrong:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function receiveConnection(db, remoteValue) {  
    try {  
        var latest  
        let current = getCounter()  
        if (current == remoteValue) {  
            db.startTransaction()  
            latest = updateCounter()  
            updateRedis(latest)  
            db.commitTransaction()  
        }  
        return latest  
    } catch {  
        db.rollbackTransaction()  
        throw(&amp;#34;crap&amp;#34;)  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So now if the &lt;code&gt;updateRedis&lt;/code&gt; call throws we can be sure the database value is rolled back. This is a repeatable function since it only works once and is a noop if called again. This seems great.&lt;/p&gt;
&lt;p&gt;But, we know that network errors can be false negatives — the call to redis could succeed even if we get an error, the value in redis is then a higher number than the value in the database again. Redis and the database would be out of sync.&lt;/p&gt;
&lt;p&gt;And we have another problem. Let me illustrate it with another amazing ASCII diagram:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function receiveConnection(db, remoteValue) {  
    try {  
        var latest  
        let current = getCounter()  
        if (current == remoteValue) {  
            db.startTransaction()  
            latest = updateCounter()  
            updateRedis(latest)  
        ---&amp;gt;  
            db.commitTransaction() // assume this throws if it fails  
        }  
        return latest  
    } catch(e) {  
        db.rollbackTransaction()  
        throw(e)  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are two things that are wrong at the marked point:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It’s impossible to know how long it takes to jump over this point in the program: from updating redis to committing the transaction. It’s also impossible to know how long it will take to fully commit the transaction. So there is some amount of time between updating redis and the transaction committing that we read a value from redis from the future. Until the transaction commits, only this caller with this transaction can read the correct value from both places. &lt;strong&gt;We can’t be consistent even with great error handling.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The program can terminate at this point. It’s possible that updateRedis fully succeeds and receives a response, but the program is terminated and the transaction is never committed. The database would eventually notice this and rollback the changes. No one except this caller would have ever seen the new value in the database, yet the new value would be present in redis. There is no way to be 100% sure our process will never die at this point.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So it seems impossible to reliable do anything across more than one computer.&lt;/p&gt;
&lt;h4 id=&#34;transactions-and-mutexes-are-only-useful-inside-one-process-on-onecomputer&#34;&gt;Transactions and Mutexes Are Only Useful Inside One Process on One Computer&lt;/h4&gt;
&lt;p&gt;Transactions work for postgres because it’s a single process on a single machine. It’s able to use mutexes and global transaction ids to prevent concurrent updates of rows while also knowing what order to apply changes. The postgres team has done a lot of work over the years to make sure things work how they should. Sure, there are &lt;a href=&#34;http://www.postgresql.org/docs/devel/static/runtime-config-wal.html#GUC-SYNCHRONOUS-COMMIT&#34;&gt;options for systems that can synchronously commit a transaction across multiple machines&lt;/a&gt;, but these are daunting solutions with heavy prices to pay in performance and operating costs. And &lt;a href=&#34;http://michael.otacoo.com/postgresql-2/track-commit-synchronous/&#34;&gt;it’s difficult to get right&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once the distance between things is more than inside a single computer (or virtual computer) things get hinky. It’s no longer possible to reliably protect against concurrent actions. And since network errors sometimes make it impossible to infer what happened on the other side: it just isn’t possible to reliably commit or rollback something.&lt;/p&gt;
&lt;p&gt;Even with more error handling, more rollbacks, more verification, it’s basically impossible to build this system with the given constraints in a simple way.&lt;/p&gt;
&lt;p&gt;With this example, we are really just caching the current value in redis. This could also be done with a simple read-through cache. In the middle of the database transaction one can clear the cache and then the next time a read comes in it will repopulate. Here is a code example:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function getValue(cache, db) {  
    let cachedValue = cache.get()

    if (cachedValue !=== null) { return cachedValue }

    let currentValue = db.get()  
    cache.set(currentValue)  
    return currentValue  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works all right: if the value is missing we just set it and return it. There is a strange thing that can happen, where if &lt;a href=&#34;https://en.wikipedia.org/wiki/Cache_stampede&#34;&gt;multiple concurrent &lt;code&gt;getValues&lt;/code&gt; are run there might be multiple processes trying to &lt;code&gt;cache.set&lt;/code&gt; at the same time&lt;/a&gt;. This is odd, but it’s not that big of a deal. This is a problem with all read-through caches — when the cache is missing all the running processes might all try to update it.&lt;/p&gt;
&lt;p&gt;What does the setValue look like in this case? (Here we are also making sure &lt;a href=&#34;http://www.postgresql.org/docs/9.0/static/sql-select.html#SQL-FOR-UPDATE-SHARE&#34;&gt;to lock the value for updating while reading it&lt;/a&gt;, so we know we have the truly latest value and it can’t change by anyone else during this transaction.)&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function setValue(cache, db, currentValue) {  
    try {  
        db.startTransaction()  
        let current = db.selectAndLockForUpdate()  
        if (current != currentValue) {  
            throw(&amp;#34;crap&amp;#34;)  
        }  
        db.incrementValue()  
        cache.clear()  
        db.commitTransaction()  
    } catch {  
        db.rollbackTransaction()  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is looking pretty good, but another ASCII diagram is in order:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function setValue(cache, db, currentValue) {  
    try {  
        db.startTransaction()  
        let current = db.selectAndLockForUpdate()  
        if (current != currentValue) {  
            db.cancelTransaction()  
            throw(&amp;#34;crap&amp;#34;)  
        }  
        db.incrementValue()  
        cache.clear()  
    ---&amp;gt;  
        db.commitTransaction()  
    } catch {  
        db.rollbackTransaction()  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;How long does the program stay at the marked point in the code? The answer is: &lt;strong&gt;there is no way to know&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We clear the cache, but then we don’t know how long it will take to actually commit the transaction. If a read were to come in to a different process during that window of time, the old value would be cached in getValue— since no one else can read the new value until this transaction is committed.&lt;/p&gt;
&lt;p&gt;We can change the code to this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function setValue(cache, db, currentValue) {  
    try {  
        db.startTransaction()  
        let current = db.selectAndLockForUpdate()  
        if (current != currentValue) {  
            throw(&amp;#34;crap&amp;#34;)  
        }  
        db.incrementValue()  
        db.commitTransaction()  
        cache.clear()  
    } catch {  
        db.rollbackTransaction()  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And now we have the old problem: there is a time window where a new value is committed to the database, but not present in the cache. There also is the possibility that the database transaction completes successfully, but the &lt;code&gt;cache.clear()&lt;/code&gt; line is never run or has a network failure. It’s also possible that the committing of the transaction only appears to fail (yet another network failure) and we don’t know what to do. Maybe we should just clear the cache everywhere.&lt;/p&gt;
&lt;p&gt;I’ve seen things like this before:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function setValue(cache, db, currentValue) {  
    try {  
        db.startTransaction()  
        let current = db.selectAndLockForUpdate()  
        if (current != currentValue) {  
            throw(&amp;#34;crap&amp;#34;)  
        }  
        db.incrementValue()  
        cache.clear()  
        try {  
            db.commitTransaction()  
        } finally {  
            cache.clear()  
        }  
    } catch {  
        db.rollbackTransaction()  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This has all the same issues. It’s possible a too early &lt;code&gt;getValue&lt;/code&gt; makes the first clearing of the cache useless and it’s possible the cache never gets cleared after the transaction finishes. It’s also pretty ugly.&lt;/p&gt;
&lt;p&gt;We could change the type of transaction we are using to block all access to the row until we are done with it. This means no one can read the row until we either commit or rollback our transaction. Since every getValue would freeze at the point where the query happens until we are done updating the database, they would have the best current value. Or so it would seem.&lt;/p&gt;
&lt;p&gt;A possible order of events is (assume the cache is currently empty, process A is about to execute getValue, and process B is about to execute setValue):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Process A begins and retrieves the current value successfully from the database (value in memory is 3)&lt;/li&gt;
&lt;li&gt;Process B fully begins and completes running setValue, updates the database row (value in the database is 4), and clears the cache (a noop, it’s currently empty)&lt;/li&gt;
&lt;li&gt;Process A proceeds to set the cache to it’s current in memory value (cache is now at 3)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So now the read-through cache set the value to 3 even though the last successful database query updated it to 4. Great.&lt;/p&gt;
&lt;p&gt;How often will this happen? That depends on scale, network topology, and a whole host of things that are probably out of your control. If they are within your control then good for you. But most of us can’t control time. Unless you’re google. Then you can. (I mean seriously, &lt;a href=&#34;http://thetechjournal.com/tech-news/google-uses-atomic-clocks-to-synchronize-its-global-spanner-database.xhtml&#34;&gt;google found a way to synchronize clocks&lt;/a&gt; so anything is “possible.”)&lt;/p&gt;
&lt;p&gt;Now, most people would call this good enough. And at small scale, this is probably good to go. The likelihood of edge cases might be low. But it turns out at even at moderate scale things happen.&lt;/p&gt;
&lt;p&gt;How important is it for your application that your data is consistent? If it can be stale for some period of time then you are fine to just set a TTL on the caches and call it done.&lt;/p&gt;
&lt;p&gt;Don’t people use caching all the time? What can we do? Can we build a truly reliable system?&lt;/p&gt;
&lt;p&gt;Let’s keep going and find out. (Also search for &lt;a href=&#34;https://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works&#34;&gt;russian doll caching&lt;/a&gt; if you want to have a reliable read-through cache.)&lt;/p&gt;
&lt;p&gt;What about the original idea?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A database that supports transactions will hold a value. The value is updated atomically and the rest of the system wants to be notified about the change in a reliable way.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This sounds like we should just:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function receiveConnection(db, currentValue, newValue) {  
    try {  
        db.startTransaction()  
        let current = db.selectAndLockForUpdate()  
        if (current == currentValue) {  
            updateValue(newValue)  
            publishChange(currentValue, newValue)  
            db.commitTransaction()  
        } else {  
            db.rollbackTransaction()  
        }  
    } catch {  
        db.rollbackTransaction()  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But this has all the problems discussed above. The worse case for this function is we could publish a value that never gets committed: a value from an alternate future, where it almost made it as the newest version of the value.&lt;/p&gt;
&lt;p&gt;If we move things around, it just changes the worst case scenario:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function receiveConnection(db, currentValue, newValue) {  
    try {  
        db.startTransaction()  
        let current = db.selectAndLockForUpdate()  
        if (current == currentValue) {  
            updateValue(newValue)  
            db.commitTransaction()  
            publishChange(currentValue, newValue)  
        } else {  
            db.rollbackTransaction()  
        }  
    } catch {  
        db.rollbackTransaction()  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we might commit something to the database that never gets published. While it’s very unlikely that the process will crash right before trying to publish the change: publishing is a network call. It will fail. And it will fail often.&lt;/p&gt;
&lt;p&gt;Even if one got really paranoid and tried to revert upon a publishing fail:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function receiveConnection(db, originalValue, newValue) {  
    try {  
        db.startTransaction()  
        let current = db.selectAndLockForUpdate()  
        if (current == originalValue) {  
            updateValue(newValue)  
            db.commitTransaction() // assume this throws if it fails  
            try {  
                publishChange(originalValue, newValue)  
            } catch {  
                db.updateValue(originalValue) // revert  
            }  
        } else {  
            db.rollbackTransaction()  
        }  
    } catch {  
        db.rollbackTransaction()  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is now even worse. We don’t know the timing between the publishing failure and the revert, so it’s possible that another process just finished updating the value again and we would revert that update (we are outside the row-lock at this point). Trying to plug holes like this is an endless game and doesn’t end well.&lt;/p&gt;
&lt;p&gt;How can we make it possible to be sure that every published change is also committed? And that every committed change is also published?&lt;/p&gt;
&lt;p&gt;We need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make this truly idempotent&lt;/li&gt;
&lt;li&gt;Make sure to retry after any type of failure&lt;/li&gt;
&lt;li&gt;Make it possible to know if it fully succeeded&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;ok-make-things-reliableplease&#34;&gt;OK, Make Things Reliable Please&lt;/h3&gt;
&lt;p&gt;The first thing one should consider is “does this really need to be reliable”? What if once a month one screen might show slightly out of date information? Is that acceptable? Maybe it is. Maybe if it saves hundreds of thousands or millions of dollars then it’s fine. It might even be possible to be aware of when that happens, give that customer a coupon for a month of free service, and just move on.&lt;/p&gt;
&lt;p&gt;However, if things must be reliable and consistent, then there is only one thing you can do: &lt;strong&gt;plan for failure.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The rest of this is going to be about how to plan for things to suck.&lt;/p&gt;
&lt;h4 id=&#34;consistency&#34;&gt;Consistency&lt;/h4&gt;
&lt;p&gt;The easiest (and usually only) way to have consistency in a system is to localize something down to one processor on one computer. This is the basic idea behind sharding, single-threaded-ness (redis), and even actors in something like Akka or Service Fabric.&lt;/p&gt;
&lt;p&gt;If we can get things down to that level, then things will work out. This is one way to plan for failure: to make sure that when things fail they are localized to one process so we know we can rollback its changes.&lt;/p&gt;
&lt;h4 id=&#34;use-thedatabase&#34;&gt;Use the Database&lt;/h4&gt;
&lt;p&gt;The easiest way to protect against network failure is to not hit the network. Instead of trying to notify every one of every change right when it happens, maybe we just try to store the changes where we are. Periodically we can just send whatever changes are stored to everyone. This isn’t “real-time” per-se, but maybe that’s all right.&lt;/p&gt;
&lt;p&gt;Since we are already using a database transaction in our pseudo code, we could just do more work there.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function receiveConnection(db, currentValue, newValue) {  
    try {  
        db.startTransaction()  
        let current = db.selectAndLockForUpdate()  
        if (current == currentValue) {  
            db.updateValue(newValue)  
            db.recordChange(currentValue, newValue)  
            db.commitTransaction()  
        } else {  
            db.rollbackTransaction()  
        }  
    } catch {  
        db.rollbackTransaction()  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this code we now know the value update and the change record will either both succeed or both fail. This is now idempotent.&lt;/p&gt;
&lt;p&gt;This could put a lot more strain on the database and possibly create scaling issues, but it does fix our consistency problem. Except it doesn’t actually notify others of changes. For that, we now need another process to regularly check for changes to send:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function checkForChanges(db) {  
    db.forEachChange(change =&amp;gt; publishChange(change))  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One thing that could happen now is notifying everyone could fail, so it’s best if the fetchChanges works kinda like:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function forEachChange(cb) {  
    let changes = db.allChanges()  
    for change in changes {  
        try {  
            db.lockChange(change.id)  
            cb(change)  
            db.deleteAndUnlockChange(change.id)  
        } catch {  
            db.unlockChange(change.id)  
        }  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This way if anything fails, then the whole process will just start over. A change is only deleted when we know it’s been sent to everyone successfully. Of course this could be made much better in a lot of ways, but this is the minimum to get it working.&lt;/p&gt;
&lt;p&gt;This leads to a scenario where it’s possible a notification appears to have failed, but succeeded–since notifying everyone is a network call. If this happens, the process will the start over and the same change will be sent a second time. &lt;strong&gt;It turns out this is fine, because this was already possible.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When dealing with sending asynchronous messages (using a message queue or something) one has to choose between at-least-once delivery or at-most-once. There really aren’t other options (unless you can control time or something like that).&lt;/p&gt;
&lt;p&gt;At-most-once isn’t acceptable for us, because that means a notification is delivered 0 or 1 times. We wanted to &lt;strong&gt;reliably&lt;/strong&gt; send notifications to all connected clients. So we would have to choose at-least-once. And at-least-once means 1 or more (so 2 or 3 or 1,000 times).&lt;/p&gt;
&lt;p&gt;So our app would already have to be ok with duplicates. We now have two ways duplicates could happen:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;publishChange&lt;/code&gt; falsely appears to have failed, and we retry&lt;/li&gt;
&lt;li&gt;Our message bus has some network issues and delivers a message twice&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is all right. We’ll have to deal with duplicate messages if we are sending messages over a network. It’s just another type of failure we plan for. (Putting a UUID on every message or something like that means we could match up ones we’ve already received; it’s a pretty “easy” problem to take care of.)&lt;/p&gt;
&lt;p&gt;So, now we have a reliable way to notify everyone of every change to the value in the database. We can setup cron or have a process with a time run checkForChanges regularly and let it run.&lt;/p&gt;
&lt;p&gt;It’s also worth considering some metrics and alerting for how often the &lt;code&gt;checkForChanges&lt;/code&gt; is run. If it’s not run within a certain time then notifications are not being sent out. It’s worth investing some time in &lt;a href=&#34;https://deadmanssnitch.com/&#34;&gt;setting up some monitoring&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If we did want things to be more real-time, we could setup a pubsub channel to throw changes down:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function receiveConnection(db, currentValue, newValue) {  
    var change

    try {  
        db.startTransaction()  
        let current = db.selectAndLockForUpdate()  
        if (current == currentValue) {  
            db.updateValue(newValue)  
            change = db.recordChange(currentValue, newValue)  
            db.commitTransaction()  
        } else {  
            db.rollbackTransaction()  
        }  
    } catch {  
        db.rollbackTransaction()  
    }  
    tryToPublishChange(change)  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;tryToPublishChange&lt;/code&gt; can lock the row, try to publish, and then delete the row if successful. If it fails, no worries–there is a process running that will eventually pickup the change and send it on it’s way. Since we lock each change while we try to publish it, there is no way someone else can read or delete it.&lt;/p&gt;
&lt;p&gt;Our system has more components, but it’s simpler. We don’t need as much error checking and we managed to just use what we had in front of us. But there is a real problem with databases: at scale, they are usually a bottleneck. Don’t worry about that until it’s a problem.&lt;/p&gt;
&lt;p&gt;We are planning for things to fail and we’ve isolated each part to it’s own process on it’s own computer. So each tiny part can be atomic, which is a huge win!&lt;/p&gt;
&lt;h3 id=&#34;write-twice-thenverify&#34;&gt;Write Twice, Then Verify&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;If the database is going to be a problem, then we gotta go a different route.&lt;/em&gt; It’s possible we are already maxing out the queries per second we can handle and we don’t want to add even more and longer locks to our write queries.&lt;/p&gt;
&lt;p&gt;If that is the case, then we could go down the route of having three things at play:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write to primary storage&lt;/li&gt;
&lt;li&gt;Write the change notification to a secondary storage place&lt;/li&gt;
&lt;li&gt;Periodically (hopefully very often or immediately) take a change, verify the update is in-fact in primary storage, then publish it&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This could look something like what we’ve seen before:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function receiveConnection(db, currentValue, newValue) {  
    try {  
        db.startTransaction()  
        let current = db.selectAndLockForUpdate()  
        if (current == currentValue) {  
            db.updateValue(newValue)  
            saveChange(currentValue, newValue)  
            db.commitTransaction()  
        } else {  
            db.rollbackTransaction()  
        }  
    } catch {  
        db.rollbackTransaction()  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We already know that it’s possible for us to save a change that doesn’t actually get committed: our program could fail after &lt;code&gt;saveChange&lt;/code&gt; and before or during &lt;code&gt;commitTransaction&lt;/code&gt;. But the third process that regularly (or constantly) checks each change to make sure that the database reflects the potential truth means we can discard changes that didn’t actually get written.&lt;/p&gt;
&lt;p&gt;It’s actually a little more complicated than that. If the external change processor is slow, it’s possible changes pile up. And with our current schema of just storing a value we could accept two writes to the value before the first change is processed. At that point we have no way of knowing if the first change really was persisted–we only know that it’s not the current value in the database.&lt;/p&gt;
&lt;p&gt;We could fix this by keeping a history somewhere, by adding some sort of clock or UUID to each change, or by just accepting that overwritten changes don’t need to be sent out as notifications. Append-only storage might be a very good option for this. Maybe we don’t care if phones get all the in-between changes, we only care that they get the final good one. If that’s the case then when things calm down and changes are processed, then everyone will find out about it.&lt;/p&gt;
&lt;p&gt;This is a much more complicated system. It involves even more components, but it also spreads the work out which might be important if our primary database is not having the best time.&lt;/p&gt;
&lt;p&gt;We are planning for some of the writes to fail and have processes in place to take that into account. There are tradeoffs, for sure, but it _will work–_for some definition of work.&lt;/p&gt;
&lt;h3 id=&#34;just-make-everything-async&#34;&gt;Just Make Everything Async&lt;/h3&gt;
&lt;p&gt;And finally, as a third (and possibly way overboard) idea: just change the entire application and it’s API.&lt;/p&gt;
&lt;p&gt;Maybe what you want is a way to submit changes, perform them one by one, and only move on to the next one when you know the first has succeeded.&lt;/p&gt;
&lt;p&gt;Our API could become a simple ordered list of operations, each wanting to change the value. We could have a function like:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function performOperation(q, db, newValue) {  
    try {  
        let current = db.getValue()  
        db.setValue(newValue)  
        publishChange(current, newValue)  
        q.markCurrentOperationComplete()  
    } catch {  
        db.rollbackTransaction()  
        q.retryCurrentOperation()  
    }  
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a very simple system which needs a rule: in the event of any failure in an operation, retry the whole thing. This means it’s possible that if the publishChange function were constantly erring we would write the same value into the db over and over, but maybe that’s fine. The other changes would queue up and only progress after we’ve gotten through the current one.&lt;/p&gt;
&lt;p&gt;This makes our function atomic and idempotent. Sure, the database will allow reads to the new value for some amount of time before the change notification is sent, but it’s not an incorrect value. We haven’t sent the change notification yet, but we will eventually. There was already some indeterminate amount of time between when we saved and when we sent the notification anyway. And we won’t have any other values until it’s sent.&lt;/p&gt;
&lt;p&gt;We are planning for &lt;code&gt;publishChange&lt;/code&gt; to fail and we are OK with the tradeoffs.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I’ve detailed lots of issues with the types of code I see in the wild, I’ve listed some options to make things reliable, and I’ve probably made some mistakes along the way. Please comment or send me a note with any feedback or fixes.&lt;/p&gt;
&lt;p&gt;Also, don’t build a distributed system if you don’t think your code or network or computer or something will fail. It will fail. Instead, just plan for it. It’s possible to consider lots of angles and just put things in place to either fix the issue or to just document it. Sometimes failure is fine, but don’t be surprised by it if possible.&lt;/p&gt;
&lt;p&gt;Whether you’re building an iOS app or deploying some code to heroku, you are working with a distributed system. Two components talking over a network are not near each other and they will eventually act like that is the case. Just be aware. Don’t let anyone tell you something is impossible. Maybe it is, but that doesn’t really matter. What matters is documenting what happens in response to failures.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Winter and small coffee</title>
      <link>https://nathanherald.com/posts/legacy/winter-and-small-coffee/</link>
      <pubDate>Wed, 04 Nov 2015 10:40:00 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/winter-and-small-coffee/</guid>
      <description>&lt;p&gt;It’s starting to get cold again. I’ve been through two winters here now and I’m already bracing for the cold that’s on the way. Where I’m from it gets colder and snows more, but it &lt;em&gt;feels&lt;/em&gt; colder here. The sun seems to disappear for a month and that makes everything feel frigid.&lt;/p&gt;
&lt;p&gt;It can be nice to have shorter days. I seem to focus more and really be intentional about getting out when the sun is out, doing errands during the daylight, and working more into the evenings since they are now the longest part of my day. Some of my colleagues take vitamin D supplements during the winter, while others really enjoy the darkness. I believe I am able to be much more productive in the winter here, since I am less inclined to want to go outside or do anything far from home. Maybe startups formed in Berlin will have natural cycles of crunch time in the winter, relaxing vacations in the summer.&lt;/p&gt;
&lt;p&gt;I also drink more coffee in the winter, but that is fine since the coffees here are so small. A large coffee here is normally less than a small where I’m from. And it’s great. Being able to have a shot of caffeine in the middle of the day without filling up with ounces and ounces of water is great.&lt;/p&gt;
&lt;p&gt;When traveling in Italy and France the coffees were even smaller. Italian coffee places didn’t offer espresso to go, instead you just stand and take it like a shot glass then go about your business. I really prefer this style of coffee intake: smaller doses, better coffee.&lt;/p&gt;
&lt;p&gt;The coffee really is better. Probably having less at a time makes it somewhat seem better, but I generally feel that the quality of coffee here is much higher than where I am from. I’m not saying it’s better than in SF, there are just too many good places in SF to choose from. But a random café in the towns and cities we’ve visited in Europe is ten times better than a random café in Virginia or the places we stopped at while driving through Wyoming. It’s possible the baseline for acceptable coffee is just higher. Everyone seems to have a decent machine and everyone seems to buy decent roasted beans.&lt;/p&gt;
&lt;p&gt;When I have revisited where I’m from I’ve been amazed by how much warmer it feels even though the temperatures are generally colder. I’m also amazed by the large coffee-like drinks that seem to be everywhere. It seems in fashion to have a large caffeinated milkshake all the time.&lt;/p&gt;
&lt;p&gt;I’ve learned to really appreciate coffee here. Having less at a time makes something more special. And I’ve also learned to appreciate the sun, for exactly the same reason.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Two years in Berlin</title>
      <link>https://nathanherald.com/posts/legacy/two-years-in-berlin/</link>
      <pubDate>Tue, 13 Oct 2015 10:06:09 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/two-years-in-berlin/</guid>
      <description>&lt;p&gt;Last weekend was Unification Weekend which means we’ve officially been in Deutschland for two years. We arrived two years ago to a holiday weekend we were not prepared for. Everything was closed, we couldn’t get food for us or the dog, and we definitely couldn’t get any cables or converters for any electronics.&lt;/p&gt;
&lt;p&gt;Holidays are something I love about Berlin and Europe: people take them. Over the last two years, it appears more and more businesses are attempting to stay open, especially cafés. However, it seems one will never be able to do much else but drink coffee or beer on a holiday. I’m fine with this.&lt;/p&gt;
&lt;p&gt;Except for Christmas. There are many Weihnachtsmärkte open. And all the national museums are open. I’ve heard it’s normal for families to celebrate together at home Christmas Eve and then spend Christmas day out and about. We used to do the same at my maternal Grandmother’s. My Great Grandmother’s last name was Amburgy, but that is the American version possibly of Amburger.&lt;/p&gt;
&lt;p&gt;For the last two years we have gotten each other annual passes to the national museums on 25 Dec. They cost as much as visiting 5 or so museums a year, plus we get to go to one on Christmas. Flying back to the USA during the “holiday season” is way too expensive, so it’s better to wait, remain in Berlin, and visit some museums.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Morning</title>
      <link>https://nathanherald.com/posts/legacy/morning/</link>
      <pubDate>Mon, 12 Oct 2015 08:59:52 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/morning/</guid>
      <description>&lt;p&gt;Early, walking the leave-lined stone sidewalks with der kleine hund, it’s impossible not to enjoy the crisp 0º morning. It’s much colder in the shadow of a building, then blinding from the low winter sun between two. Yes, it’s winter. When it’s not summer it’s winter and that is fine by me.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>How we allow any request to be safely repeated at anytime for Wunderlist</title>
      <link>https://nathanherald.com/posts/legacy/how-we-allow-any-request-to-be-safely-repeated-at-anytime-for-wunderlist/</link>
      <pubDate>Tue, 24 Mar 2015 12:17:09 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/how-we-allow-any-request-to-be-safely-repeated-at-anytime-for-wunderlist/</guid>
      <description>&lt;figure&gt;
  &lt;a href=&#34;https://www.flickr.com/photos/thomashawk/2681744739&#34;&gt;
  &lt;img
    alt=&#34;A mechanical button reading &amp;#39;repeat&amp;#39;&#34;
    width=&#34;1200&#34;
    height=&#34;800&#34;
    src=&#34;https://nathanherald.com/posts/legacy/how-we-allow-any-request-to-be-safely-repeated-at-anytime-for-wunderlist/1-fHM7VzsKEk37wIrfhSkNWA_hu7305689171836239334.jpeg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Via &lt;a href=&#34;https://www.flickr.com/photos/thomashawk/2681744739&#34;&gt;https://www.flickr.com/photos/thomashawk/2681744739&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Stripe recently introduced an &lt;a href=&#34;https://stripe.com/docs/api#idempotent_requests&#34;&gt;Idempotent requests feature&lt;/a&gt; for their api calls to protect against duplicate charges caused by network failures. While building Wunderlist 3 we needed a similar mechanism for our remote clients to be able to be safely repeat any requests. Mobile networks, corporate firewalls, and general internet connectivity issues are problems way too often. One cannot be sure an operation has successfully arrived without receiving a full, parseable response.&lt;/p&gt;
&lt;p&gt;We took a different route to protecting our data and I’d like to detail it for future reference. It could be useful for other’s to compare and contrast Stripe’s and Wunderlist’s methods for idempotent requests.&lt;/p&gt;
&lt;p&gt;With stripe, one includes an &lt;code&gt;Idempotency-Key&lt;/code&gt; header with every request. If a request comes in within the next 24 hours with the same header value then Stripe will return the original response. One could build something like this utilizing redis or memcached to hold all raw responses keyed by the idempotency header’s value with a TTL. 24 hours makes sense for stripe since their clients are mostly background workers on servers. Surely even 1 hour would probably be enough, since most server workers are always working and would retry very quickly.&lt;/p&gt;
&lt;p&gt;For Wunderlist, we have clients in remote places that sometimes are offline for days. A user might use their iPhone constantly for weeks, but only open their iPad once every week. Or a person might be on a web client at work, but a phone at home. Instead of using an in-memory cache of responses, we decided to let our databases do most of the work for us. I always favor the dumbest solution that works and so that is what we did (:&lt;/p&gt;
&lt;p&gt;All GET requests (which are always reads) to the Wunderlist apis are safe already, so that part is easy.&lt;/p&gt;
&lt;p&gt;For write operations every Wunderlist client maintains an an operation queue that is serializable to disk. Every write request is first added to the operation queue. Then, on the other side, an operation worker marks items as in-progress, finished, etc as it processes the requests. Right before a client is quit: it writes the current state to disk. This state is restored into memory on app boot. A request could be cut short by network issues, memory pressure, or just the use quitting the app. The request could have succeeded or it could have never made it to the server, it’s impossible for the client to know.&lt;/p&gt;
&lt;p&gt;POSTs (which are always creates) are the hardest since the client has zero knowledge about the server’s state. PUTs (which are updates) are easier, since the client already knowns the server’s identifier for the object being updated. And we decided DELETEs always win and always succeed.&lt;/p&gt;
&lt;p&gt;PUTs are easy because our sync algorithm already handles them. Every object in the Wunderlist system has an identifier and a revision. Every update to an object safely increments it’s revision counter by at least 1. Anytime a client updates an object it must send what it knows to be the current revision and identifier along with the properties to update. If the client’s assumed current revision is incorrect, the server immediately rejects the request and asks the client to fetch and merge the newest information. I am not ashamed to say this is identical to how Couch DB works, which is where we got the idea. If a client were to replay a successful update, it would fail because the revision would have changed and the client would be out of date. The client would fetch the new information, merge that in, notice nothing is out of date, and reconcile the operation in the queue to be completed.&lt;/p&gt;
&lt;p&gt;For all POST requests, the client generates a header we named &lt;code&gt;X-Client-Request-ID&lt;/code&gt; which is basically the same as stripe’s &lt;code&gt;Idempotent-Key&lt;/code&gt; header. This request id is generated as part of the request operation that is added to the in-memory queue, this way it is serialized to and from disk as needed. This means a client can attempt to replay a POST request even if the app was offline for a long time.&lt;/p&gt;
&lt;p&gt;On the server side, we let the database do the hard work for us. Every table for all synced objects has the aforementioned id and revision columns along with a &lt;code&gt;created_by_request_id&lt;/code&gt; column. The request id column has a unique constraint, so there can never be two. To prevent clients from stomping on top of each other’s request id’s, other metadata like the current user’s id and the current device’s identifier are prepended to the already random request id. Since the operation queue is not synced between devices that is enough to keep this super unique.&lt;/p&gt;
&lt;p&gt;Our apis return the &lt;code&gt;created_by_request_id&lt;/code&gt; field in the object’s json so that clients can reconcile any operations currently stuck in their operation queue without having to retry the request.&lt;/p&gt;
&lt;p&gt;All Wunderlist clients only perform GETs during a sync and only perform any writes when flushing the operation queue. If a client is currently in read mode then it will scan through the operation queue for each newly downloaded object’s request id, attempting to reconcile an in-memory request if one is found. In write mode: the client will simply send every request from the operation queue.&lt;/p&gt;
&lt;p&gt;Since deletes always win they can safely be replayed over and over. The object won’t get any more deleted.&lt;/p&gt;
&lt;p&gt;Although there are multiple concepts involved, we have found this system for idempotency to be very easy to understand and very reliable. What really pays is to consider the system in it’s entirety (from client to server) to come up with the best possible solution. Stripe’s is very elegant and could work for a certain type of app. Our solution is tailored for our long term, long range duplicate request issues. I hope this is helpful in some way if you are thinking about these things for your app.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href=&#34;http://myobie.svbtle.com/how-we-allow-any-request-to-be-safely-repeated-at-anytime-for-wunderlist&#34;&gt;&lt;em&gt;myobie.svbtle.com&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. Yes – I&amp;rsquo;ve had many blogs in the past…&lt;/em&gt;&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>My core values</title>
      <link>https://nathanherald.com/posts/legacy/my-core-values/</link>
      <pubDate>Wed, 18 Mar 2015 10:39:52 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/my-core-values/</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Other’s needs and wants are more important than your own&lt;/li&gt;
&lt;li&gt;Surprise and delight those you connect with&lt;/li&gt;
&lt;li&gt;Thank people regularly for what they do&lt;/li&gt;
&lt;li&gt;Take risks from time to time&lt;/li&gt;
&lt;li&gt;Ambitiously pursue solutions for hard problems&lt;/li&gt;
&lt;li&gt;Positively encourage other’s ideas and aspirations&lt;/li&gt;
&lt;li&gt;Be content, but not complacent with how things are currently&lt;/li&gt;
&lt;li&gt;Prioritize calmly, work aggressively&lt;/li&gt;
&lt;li&gt;Gladly do the work other’s don’t want&lt;/li&gt;
&lt;li&gt;Focus on details while considering a work holistically&lt;/li&gt;
&lt;/ul&gt;

        
      </description>
    </item>
    <item>
      <title>Make workflows for complicated tasks</title>
      <link>https://nathanherald.com/posts/legacy/make-workflows-for-complicated-tasks/</link>
      <pubDate>Mon, 26 Jan 2015 09:20:52 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/make-workflows-for-complicated-tasks/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been extracting a lot of controller code into simple POROs recently, but it&amp;rsquo;s become more and more difficult and repetitive to get things to work consistently. I end up doing a lot of &lt;code&gt;if&lt;/code&gt; statements in the &lt;code&gt;#call&lt;/code&gt; method to manage failure states. An example might be:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# in a controller&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;create&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:amount&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;to_i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;unless&lt;/span&gt; &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;render&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:error&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;and&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;begin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;vi&#34;&gt;@charge&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Stripe&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;Charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;ss&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;ss&#34;&gt;card&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:card_token&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;rescue&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Stripe&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;CardError&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;render&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:error&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;and&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@charge_response&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;StripeChargeResponse&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;vi&#34;&gt;@charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;to_hash&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Payment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;stripe_charge_response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge_response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;stripe_charge_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;currency&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;currency&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;begin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;save!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;rescue&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;RecordInvalid&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;render&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:error&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;and&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;no&#34;&gt;ReceiptMailer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;payment_receipt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;deliver_later&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;redirect_to&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;receipt_path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After some work, I decided I should extract each step of the operation into it&amp;rsquo;s own method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# in a controller&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;create&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;grab_amount&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;n&#34;&gt;save_payment&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;n&#34;&gt;send_email&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;redirect_to&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;receipt_path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;render&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;grab_amount&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:amount&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;to_i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;charge&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@charge&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Stripe&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;Charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;card&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:card_token&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kp&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;rescue&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Stripe&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;CardError&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;save_payment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@charge_response&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;StripeChargeResponse&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;vi&#34;&gt;@charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;to_hash&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Payment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;stripe_charge_response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge_response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;stripe_charge_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;currency&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;currency&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;save!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;rescue&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;RecordInvalid&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;send_email&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;no&#34;&gt;ReceiptMailer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;payment_receipt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;deliver_later&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kp&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After that, I realized if I had a method to wrap and capture failures I could cleanup things even more:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# in a controller&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;create&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;grab_amount&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;n&#34;&gt;save_payment&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;n&#34;&gt;send_email&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;redirect_to&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;receipt_path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;render&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;define_and_capture&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;yield&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;rescue&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;StandardError&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kp&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;define_and_capture&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:grab_amount&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:amount&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;to_i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;define_and_capture&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:charge&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@charge&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Stripe&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;Charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;card&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:card_token&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kp&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;define_and_capture&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:save_payment&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@charge_response&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;StripeChargeResponse&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;vi&#34;&gt;@charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;to_hash&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Payment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;stripe_charge_response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge_response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;stripe_charge_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;currency&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;currency&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;save!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;define_and_capture&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:send_email&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;no&#34;&gt;ReceiptMailer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;payment_receipt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;deliver_later&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kp&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And this is way too much going on in the controller, IMHO. So making a service object for this is pretty simple:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# in a controller&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;create&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;vi&#34;&gt;@service&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;ChargeACard&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:amount&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:card_token&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;vi&#34;&gt;@service&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;call&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;redirect_to&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;receipt_path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;vi&#34;&gt;@service&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;payment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;render&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# in it&amp;#39;s own file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ChargeACard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kp&#34;&gt;attr_reader&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:payment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;initialize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;card_token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;amount&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;vi&#34;&gt;@card_token&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;card_token&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;call&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;grab_amount&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       &lt;span class=&#34;n&#34;&gt;save_payment&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       &lt;span class=&#34;n&#34;&gt;send_email&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;kp&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;kp&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;define_and_capture&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;yield&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;rescue&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;StandardError&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;e&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kp&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;define_and_capture&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:grab_amount&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:amount&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;to_i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;define_and_capture&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:charge&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;vi&#34;&gt;@charge&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Stripe&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;Charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;ss&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;vi&#34;&gt;@amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;ss&#34;&gt;card&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:card_token&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kp&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;define_and_capture&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:save_payment&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;vi&#34;&gt;@charge_response&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;StripeChargeResponse&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;vi&#34;&gt;@charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;to_hash&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Payment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;ss&#34;&gt;stripe_charge_response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge_response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;ss&#34;&gt;stripe_charge_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;ss&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;ss&#34;&gt;currency&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;charge&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;currency&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;save!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;define_and_capture&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:send_email&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;no&#34;&gt;ReceiptMailer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;payment_receipt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;vi&#34;&gt;@payment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;deliver_later&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kp&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I iterated on this more and then decided I should just package up the repeatable bits into a module which I am now publishing as a gem: &lt;a href=&#34;https://github.com/myobie/workout&#34;&gt;Workout&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Workout can help declare the steps needed to work through something. If any step fails then execution halts. A workflow instance knows if it&amp;rsquo;s completed, valid, or successful. This means a lot of controller actions can return to the simple and amazing if success then render success, else render error.&lt;/p&gt;
&lt;p&gt;Most service object type libraries I see online accept their arguments into the call method, but I don&amp;rsquo;t like this approach. I&amp;rsquo;ve made the mistake of setting instance vars in methods and those might get carried over. To me, a better approach is to always &lt;code&gt;Thing.new(args).call&lt;/code&gt; each time instead.&lt;/p&gt;
&lt;p&gt;I hope someone might also find this type of object useful.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Object Oriented System Architecture</title>
      <link>https://nathanherald.com/posts/legacy/object-oriented-server-architecture/</link>
      <pubDate>Fri, 23 Jan 2015 15:41:30 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/object-oriented-server-architecture/</guid>
      <description>&lt;p&gt;Building large systems to process web requests, work jobs, and do other things can be daunting without a plan of attack or a system-of-thought. How many components to build, how to separate responsibilities, and when to build small or big are questions that come up over and over again. Having a framework to answer questions is a huge help and keeps things consistent, especially when working on a team.&lt;/p&gt;
&lt;p&gt;The best way I’ve found to describe my system-of-thought for building large systems is object oriented system architecture. This means to loosely apply object oriented software design principles to the macro-level of a system&amp;rsquo;s architecture. Basically: micro-services. The term micro-service is pretty vague now-a-days, so I feel it’s important to be more specific.&lt;/p&gt;
&lt;p&gt;Over the next few months I plan to take the time to describe different principles, scenarios, and ideas about how to build large systems. The basics are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An object is a service&lt;/li&gt;
&lt;li&gt;Build small, well-defined objects&lt;/li&gt;
&lt;li&gt;Objects collaborate by passing messages&lt;/li&gt;
&lt;li&gt;Model different problem domains for the system as discrete objects&lt;/li&gt;
&lt;li&gt;Expose well-defined attributes and methods&lt;/li&gt;
&lt;li&gt;Document and distinguish query and command methods&lt;/li&gt;
&lt;li&gt;Objects own their data, which includes their data-store&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A simple example would be a to-do list (I know, I know). There would need to be a lists service and a tasks service at a minimum. The lists service cannot talk to the tasks data-store, instead it must query the tasks service by passing a message to a query method. Things can be broken down even further by separating task reads, task writes, and tasks storage. It is not unthinkable to have a TasksGet, TasksSave, and TasksRepository object in a class based programming language, therefore it’s not unthinkable to have a tasks-get, tasks-save, and tasks-repository service as part of the system architecture.&lt;/p&gt;
&lt;p&gt;If I built a service like this then the tasks service would expose an HTTP JSON api for all queries and commands. I would also have an asynchronous queue to facilitate cleanup work, cascade deletes, etc. The tasks service might message itself to cascade a change from one set of tasks to another by enqueuing a message onto a queue for a worker to later pickup.&lt;/p&gt;
&lt;p&gt;By thinking on the macro-level about objects, one is free to think about the micro-level more simply. Most small, well-defined services don’t need large software object hierarchies. Each query and command could be defined as a single function, possibly in a very functional language, as long as it’s exposed in a well defined way to the rest of the system.&lt;/p&gt;
&lt;p&gt;Inheritance is even possible by delegating calls from one service to another. A completed-tasks-get service might proxy every message to the tasks-get service, then filter out all non-completed tasks before returning results. Deciding if completed tasks are another service or a method on an existing service is similar to deciding if one should add a method to a class or object or to write a simple delegate object wrapper.&lt;/p&gt;
&lt;p&gt;Code is supposed to express the intent of the program and this is more easily achieved if the service has one clearly defined purpose. A service might need sophisticated permissions rules so one might choose a language better at expressing lists of rules or logic programming. Some services might just read and return data with minimal transformation and be represented as a simple function conceptually as well as in code.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Always use a CDN</title>
      <link>https://nathanherald.com/posts/legacy/use-a-cdn-for-every-site-you-build/</link>
      <pubDate>Sat, 17 Jan 2015 16:11:09 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/use-a-cdn-for-every-site-you-build/</guid>
      <description>&lt;p&gt;Doing maintenance on a 6 year old project today that &amp;ldquo;didn&amp;rsquo;t have the budget&amp;rdquo; for a &lt;a href=&#34;(https://en.wikipedia.org/wiki/Content_delivery_network)&#34;&gt;CDN&lt;/a&gt; reminded me how important one can be. I&amp;rsquo;m writing this to remind myself to stick to my guns and always make time for the important things: one of which is using &lt;a href=&#34;https://aws.amazon.com/cloudfront/&#34;&gt;Cloudfront&lt;/a&gt; or &lt;a href=&#34;https://www.cloudflare.com&#34;&gt;Cloudflare&lt;/a&gt; every time.&lt;/p&gt;
&lt;p&gt;Not every site needs to &amp;ldquo;scale&amp;rdquo; (whatever that means), but it&amp;rsquo;s a complete waste of resources to keep answering requests for the same files over and over again. If a project is hosted on heroku, then there is &lt;a href=&#34;https://devcenter.heroku.com/articles/http-caching&#34;&gt;no web server in front of the application&lt;/a&gt; to intercept requests for files. The application has to answer and handle the same requests over and over again. Even if an app is behind a reverse proxy like apache or nginx, the proxy is still answering and streaming files when it doesn&amp;rsquo;t need to.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t under estimate just how easy and useful it is to save money by reducing the requests per second hitting the application boxes. While your site is keeping up fine now, when a spike happens it can deal with more traffic if it only has to serve the dynamic bits. If a page has 4 assets, then a 100% increase is page views could mean a 400% increase in requests per second. A page with lots of logos or icons or whatever might have 100s of assets and every page view is multiplied that much. The simple math is: don&amp;rsquo;t serve assets yourself.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re willing to repoint the primary domain&amp;rsquo;s DNS then using Cloudflare is super simple. Sign up, then change DNS settings. Sadly, &lt;a href=&#34;https://support.cloudflare.com/hc/en-us/articles/203689034-Does-CloudFlare-provide-me-with-a-CDN-subdomain-or-hostname-&#34;&gt;Cloudflare does not provide a hostname&lt;/a&gt; to use, so letting them proxy the entire domain is the only way.&lt;/p&gt;
&lt;p&gt;My favorite CDN is Amazon&amp;rsquo;s Cloudfront and it&amp;rsquo;s amazingly simple to setup. All CDN&amp;rsquo;s work the same: the &lt;strong&gt;origin&lt;/strong&gt; server is the application&amp;rsquo;s primary domain. Amazon provides a unique hostname to use, just append the original file&amp;rsquo;s path. When Amazon doesn&amp;rsquo;t have a file in it&amp;rsquo;s cache it will ask the origin domain for a fresh copy at that path. All one has to do is prepend the hostname like: &lt;code&gt;https://abcdefg123.cloudfront.net/images/cats.gif&lt;/code&gt;. Another great thing is Amazon provides SSL for all Cloudfront subdomains, so no mixed content warnings or anything like that.&lt;/p&gt;
&lt;p&gt;I just recently set this up for a rails project hosted on heroku by simply making this change to &lt;code&gt;config/production.rb&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;config.action_controller.asset_host = 
  &amp;quot;https://abcdefg123.cloudfront.net&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make things easier for myself I just set it to always use &lt;code&gt;https&lt;/code&gt; even if the page including the asset is &lt;code&gt;http&lt;/code&gt;. It&amp;rsquo;s possible to use URLs like &lt;code&gt;//abcdefg123.cloudfront.net/images/cats.gif&lt;/code&gt; without the protocol which tells the browser &amp;ldquo;use the same protocol as the base document&amp;rdquo; kinda sorta. Try it out if you haven&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;For any other type of application or framework it&amp;rsquo;s pretty simple to do something like this pseudo code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = function asset_path(path) {
  if ($application.env === &amp;quot;production&amp;quot;) {
    return $application.config.asset_host + path;
  } else {
    return path;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Yeah, that&amp;rsquo;s javascript referencing a global named &lt;code&gt;$application&lt;/code&gt;. Sorry.) The basic idea is to make sure every time the path is output for an asset that it is &amp;ldquo;rendered&amp;rdquo; so in production it can use a different host.&lt;/p&gt;
&lt;p&gt;Since Cloudflare takes over the DNS they let any request for a file not currently cached to pass through. This means less configuration up front, but it &lt;em&gt;can&lt;/em&gt; lead to confusion if it&amp;rsquo;s a person&amp;rsquo;s first time using a CDN. It&amp;rsquo;s possible to configure Cloudfront to take over the origin domain as well, but it&amp;rsquo;s not required with Amazon&amp;rsquo;s setup.&lt;/p&gt;
&lt;p&gt;After setting up a CDN and making sure all assets have the correct hostname in the URLs, it&amp;rsquo;s important to set the correct headers so the CDN knows how long to keep the files. &lt;a href=&#34;https://devcenter.heroku.com/articles/increasing-application-performance-with-http-cache-headers&#34;&gt;Heroku has a great overview of HTTP caching&lt;/a&gt; and you should read it. The simplest thing to do is to add a &lt;code&gt;Cache-Control&lt;/code&gt; header for every asset requested by the CDN backend:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Cache-Control:public, max-age=31536000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is precisely what rails does when it serves an asset in production, so I got pretty lucky there. All good web frameworks will do the same thing. If you&amp;rsquo;re project is framework-less or using some new-awesome-hipster-cool-thing-framework then it should be simple to add this header when an asset is requested: if it&amp;rsquo;s not then provide a patch or module or re-consider your choice.&lt;/p&gt;
&lt;p&gt;Finally, &lt;strong&gt;check the server access logs to see the new found amazingness!&lt;/strong&gt; Offloading traffic can get way more money out of the application boxes and might even allow one to scale down. Not enough writing online talks about scaling down instead of up.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Why make a Mash?</title>
      <link>https://nathanherald.com/posts/legacy/why-make-a-mash/</link>
      <pubDate>Fri, 19 Dec 2014 07:45:09 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/why-make-a-mash/</guid>
      <description>&lt;h2 id=&#34;hashiehttpsgithubcomintrideahashie-is-fine&#34;&gt;&lt;a href=&#34;https://github.com/intridea/hashie&#34;&gt;Hashie&lt;/a&gt; is fine&lt;/h2&gt;
&lt;p&gt;Recently, Richard Schneeman wrote a very good article titled &lt;a href=&#34;http://www.schneems.com/2014/12/15/hashie-considered-harmful.html&#34;&gt;Hashie Considered Harmful - An Ode to Hash and OpenStruct&lt;/a&gt;. Give it a read, there is some wisdom there. However, I have a bit of a different take on this issue. I’ve also had this as a draft in Svbtle for way too long.&lt;/p&gt;
&lt;p&gt;First, let’s get this straight: if &lt;code&gt;OpenStruct&lt;/code&gt; is useful then &lt;code&gt;Hashie::Mash&lt;/code&gt; is useful too. And &lt;code&gt;OpenStruct&lt;/code&gt; &lt;strong&gt;is&lt;/strong&gt; really useful. Also, don’t let anyone tell you “you don’t need a hash-like object that responds to methods” because you very well might need it. Always contrast your goals and the implementation  of a library to make sure it’s as simple as it could be.&lt;/p&gt;
&lt;p&gt;Second, don’t take advice about what to use from people who can’t explain the pain or joy around it. It’s like someone who says to use postgres instead of mysql, but has no clear reason to prefer anything. What is the real pain here? What is the real benefit? What circumstances were there?&lt;/p&gt;
&lt;p&gt;To be clear, Richard explains that misspellings, insensitive access to hash-like object keys, and increased memory usage can cause issues, and he is correct. However, from certain perspectives, the tools shouldn’t try to help with misspellings at all: Javascript objects don’t raise on missing keys. Memory usage is relative to each application and my libs are not generally my problem, so we differ here too.&lt;/p&gt;
&lt;p&gt;I will try to detail why I always tell people to use what they want, but they probably don’t need &lt;code&gt;Hashie&lt;/code&gt; anyway.&lt;/p&gt;
&lt;h2 id=&#34;why-not-to-use-hashie&#34;&gt;Why not to use &lt;code&gt;Hashie&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;There is one very good reason not to use &lt;code&gt;Hashie::Mash&lt;/code&gt; at all that I don’t see explained very often: &lt;code&gt;#zip&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ gem install hashie
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ irb -rhashie
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;address&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Hashie&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;Mash&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;street&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;100 Street St&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;city&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;city&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;zip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10119&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;zip&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt; [[[&amp;#34;street&amp;#34;, &amp;#34;100 Street St&amp;#34;]], [[&amp;#34;city&amp;#34;, &amp;#34;city&amp;#34;]], [[&amp;#34;zip&amp;#34;, 10119]]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It’s honestly that simple. &lt;code&gt;Mash&lt;/code&gt; inherits from &lt;code&gt;Hash&lt;/code&gt; which includes &lt;code&gt;Enumerable&lt;/code&gt; and you have a huge number of keys (175) that have surprising behavior. However, this does not mean &lt;code&gt;Hashie&lt;/code&gt; is bad or not useful, it’s just how it works and one needs to know that.&lt;/p&gt;
&lt;h2 id=&#34;what-to-use-instead&#34;&gt;What to use instead&lt;/h2&gt;
&lt;h3 id=&#34;maybe-openstruct-is-better&#34;&gt;Maybe &lt;code&gt;OpenStruct&lt;/code&gt; is “better”&lt;/h3&gt;
&lt;p&gt;No, &lt;code&gt;OpenStruct&lt;/code&gt; is slow. Let’s see some data comparing it to &lt;code&gt;Hashie&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ gem install benchmark-ips hashie
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ irb -rbenchmark/ips -rhashie -rostruct
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;no&#34;&gt;Benchmark&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ips&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;report&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;ostruct&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;OpenStruct&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;street&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;100 Street St&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;street&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;report&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;hashie&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Hashie&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;Mash&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;street&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;100 Street St&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;street&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Calculating -------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;             ostruct    12.509k i/100ms
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              hashie    23.823k i/100ms
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;-------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;             ostruct    135.329k (± 7.8%) i/s -    675.486k
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              hashie    313.649k (± 5.9%) i/s -      1.572M
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Hashie&lt;/code&gt; is at least twice as fast&lt;/strong&gt; for the simple case of building a hash-like object and calling a method on it. This is what my normal usage of these tools looks like, grab some data and call methods on the resulting objects.&lt;/p&gt;
&lt;h3 id=&#34;oh-why-would-one-use-openstruct-then&#34;&gt;Oh, why would one use “OpenStruct” then?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;OpenStruct&lt;/code&gt; compiles the method into the instance so repeated calls will be fast. Here is what that looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ gem install benchmark-ips hashie
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ $ irb -rbenchmark/ips -rhashie -rostruct
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;no&#34;&gt;Benchmark&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ips&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;report&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;ostruct&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;o&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;OpenStruct&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;street&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;100 Street St&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;times&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;o&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;street&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;report&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;hashie&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;m&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Hashie&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;Mash&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;street&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;100 Street St&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;times&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;street&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Calculating -------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;             ostruct     4.563k i/100ms
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              hashie     1.363k i/100ms
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;-------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;             ostruct     46.592k (± 3.7%) i/s -    232.713k
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              hashie     13.598k (± 4.1%) i/s -     68.150k
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;OpenStruct&lt;/code&gt; is over three times faster&lt;/strong&gt; for repeated calls to keys. So for long lived objects, &lt;code&gt;OpenStruct&lt;/code&gt; is way better than &lt;code&gt;Hashie&lt;/code&gt;. However, there is something &lt;strong&gt;even better&lt;/strong&gt; for long lived objects: &lt;code&gt;Struct&lt;/code&gt;. If your objects are really that long lived you will probably know their schema and you can just make a class (&lt;code&gt;Struct&lt;/code&gt; is a class factory, so use it) that conforms to that schema.&lt;/p&gt;
&lt;h3 id=&#34;what-does-this-mean&#34;&gt;What does this mean?&lt;/h3&gt;
&lt;p&gt;What it always means: the tools one chooses to use should be tailored to the use case.&lt;/p&gt;
&lt;p&gt;I build a lot of apis and those apis all produce and consume JSON which in ruby is best represented as &lt;code&gt;Hash&lt;/code&gt;’s or &lt;code&gt;Array&lt;/code&gt;’s of &lt;code&gt;Hash&lt;/code&gt;’s. However, one of these lines of code is prettier:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;task_ids&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tasks&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;task_ids&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tasks&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;task&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;task&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;“&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;”&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are other examples too where using methods is much preferred from a stylistic point of view. My apis change a lot at first, so dynamically providing the &lt;code&gt;Hash#keys&lt;/code&gt; as methods allows me to move quicker. It’s possible that eventually I would define a &lt;code&gt;Struct&lt;/code&gt; for each version of each api later, which is an easy refactor since the tests all still pass because nothing really changes.&lt;/p&gt;
&lt;p&gt;If we shouldn’t use &lt;code&gt;Hashie&lt;/code&gt; and &lt;code&gt;OpenStruct&lt;/code&gt; is slow, what do we do?&lt;/p&gt;
&lt;h2 id=&#34;i-made-my-own-mash&#34;&gt;I made my own Mash&lt;/h2&gt;
&lt;p&gt;Yeah, I know, NIH and all that. But, as I typed above, evaluate tools on what they are being or will be used for. For my api producing/consuming applications I need hash wrappers that are fast and use very little memory. These wrapped objects are not long lived.&lt;/p&gt;
&lt;p&gt;My library is called &lt;a href=&#34;https://github.com/myobie/mashed&#34;&gt;&lt;code&gt;Mashed&lt;/code&gt;&lt;/a&gt;. It does three things: provides indifferent access in a predictable way, provides a hash wrapper that has a very small method footprint, and represents the internal hash’s keys as methods.&lt;/p&gt;
&lt;h3 id=&#34;indifferent-access&#34;&gt;Indifferent access&lt;/h3&gt;
&lt;p&gt;Symbols in ruby are kinda annoying. Until 2.2 (due out very soon I guess) they are not garbage collected, so technically you could allow anyone to DDOS your app by making every JSON object into a symbolized hash. (I’ve considered just going down this road and making sure I monitor my app correctly, but I’ve never actually done it.) Luckily this will go away when they are garbage collected and I will even change my implementation when that happens.&lt;/p&gt;
&lt;p&gt;But for now, I created the &lt;a href=&#34;https://github.com/myobie/mashed#stringyhash&#34;&gt;&lt;code&gt;StringyHash&lt;/code&gt;&lt;/a&gt;. It &lt;strong&gt;does not&lt;/strong&gt; inherit from &lt;code&gt;Hash&lt;/code&gt;, but instead wraps and delegates to a hash instance. The &lt;a href=&#34;https://github.com/myobie/mashed/blob/master/lib/mashed/stringy_hash.rb&#34;&gt;method footprint&lt;/a&gt; is small and it doesn’t extend any built-in ruby classes at all.&lt;/p&gt;
&lt;p&gt;The example from the README should explain it all:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;no&#34;&gt;StringyHash&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Mash&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;StringyHash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;StringyHash&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;starred&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kp&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;completed_at&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kp&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;keys&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt; [&amp;#34;title&amp;#34;, &amp;#34;starred&amp;#34;, &amp;#34;completed_at&amp;#34;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;:title&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt; &amp;#34;Hello&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt; &amp;#34;Hello&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Title&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;to_s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;sh&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;Title&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt; &amp;#34;Hello&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The goal is to be a very sensible delegator to the internal hash instance. I’ve had zero issues so far with this in many production systems. For 2.2 I will make a &lt;code&gt;SymbolizedHash&lt;/code&gt; class I guess.&lt;/p&gt;
&lt;h3 id=&#34;wrapper-with-very-small-method-footprint&#34;&gt;Wrapper with very small method footprint&lt;/h3&gt;
&lt;p&gt;In ruby, every object has a lot of built-in methods.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;no&#34;&gt;Object&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;methods&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;count&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt; 55&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Every ruby object has at least 55 methods. If the goal is to provide almost any key that might be set in a &lt;code&gt;Hash&lt;/code&gt; as a method, that is 55 keys that are impossible to get to. Luckily, ruby allows one to start from a smaller point with &lt;code&gt;BasicObject&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;no&#34;&gt;BasicObject&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;methods&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;count&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# NoMethodError: undefined method `methods&amp;#39; for #&amp;lt;BasicObject:0x007fd454b8fdc8&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That’s right, it doesn’t even know what it’s list of methods are. My &lt;a href=&#34;https://github.com/myobie/mashed/blob/master/lib/mashed/mash.rb&#34;&gt;&lt;code&gt;Mash&lt;/code&gt;&lt;/a&gt; inherits from &lt;code&gt;BasicObject&lt;/code&gt; and provides a very small amount of built-in methods.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ gem install mashed
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ irb -rmashed
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;no&#34;&gt;Mashed&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;Mash&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({})&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;methods&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;count&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt; 26&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I’m always trying to get that number lower as well. Please, if you ever have ideas for how to do that then make a &lt;a href=&#34;https://github.com/myobie/mashed/pulls&#34;&gt;PR&lt;/a&gt; or &lt;a href=&#34;https://github.com/myobie/mashed/issues&#34;&gt;Issue&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;delegate-methods-to-keyvalue-lookups&#34;&gt;Delegate methods to key/value lookups&lt;/h3&gt;
&lt;p&gt;Now, how does &lt;code&gt;Mash&lt;/code&gt; fare in the &lt;code&gt;#zip&lt;/code&gt; example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ gem install mashed
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ irb -rmashed
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;address&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;Mashed&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;Mash&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;street&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;100 Street St&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;city&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;city&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;zip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10119&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;zip&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt; 10119&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It works in an unsurprising manner. The “secret” to &lt;code&gt;Mash&lt;/code&gt; being a good citizen is for it not to be hash-like at all.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;zip&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# NoMethodError: private method `[]&amp;#39; called for #&amp;lt;Mashed::Mash:0x007fb501049cd8&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;merge&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;VA&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# NoMethodError: undefined method `merge&amp;#39; for #&amp;lt;Mashed::Mash:0x007fb501049cd8&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;map&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;puts&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;#{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;#{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# NoMethodError: undefined method `map&amp;#39; for #&amp;lt;Mashed::Mash:0x007fb501049cd8&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;inspect&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# =&amp;gt; &amp;#34;#&amp;lt;Mashed::Mash @hash=&amp;gt;{\&amp;#34;street\&amp;#34;=&amp;gt;\&amp;#34;100 Street St\&amp;#34;, \&amp;#34;city\&amp;#34;=&amp;gt;\&amp;#34;city\&amp;#34;, \&amp;#34;zip\&amp;#34;=&amp;gt;10119}&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;It just refused to appear to be a &lt;code&gt;Hash&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are still problems, one of which is &lt;a href=&#34;https://github.com/myobie/mashed/issues/4&#34;&gt;an issue right now&lt;/a&gt;: single method calls with zero arguments return &lt;code&gt;nil&lt;/code&gt; if the key is missing. This is inevitable based on the current design constraints: &lt;code&gt;Mash&lt;/code&gt; acts like a Javascript &lt;code&gt;Object&lt;/code&gt; where missing keys are &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I find this unsurprising since accessing a missing key on a &lt;code&gt;Hash&lt;/code&gt; returns &lt;code&gt;nil&lt;/code&gt;. However, I am considering making a monad or something to possibly make it easier to understand.&lt;/p&gt;
&lt;h2 id=&#34;why-is-hashie-not-like-mashed&#34;&gt;Why is &lt;code&gt;Hashie&lt;/code&gt; not like &lt;code&gt;Mashed&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;Because it’s a different tool. &lt;code&gt;Hashie&lt;/code&gt; is actually a great library and everyone should not only try to use it at least once, but read through it’s code. You can learn a ton by seeing how other’s have solved similar problems.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;OpenStruct&lt;/code&gt; is awesome too. If you’re making a ruby script and you want to have no dependencies outside the standard library then use it; this happens to me when I’m working on build or deployment scripts.&lt;/p&gt;
&lt;h2 id=&#34;use-what-works-for-youre-current-situation&#34;&gt;Use what works for you’re current situation&lt;/h2&gt;
&lt;p&gt;Write tests, evaluate libraries based on their implementation and api, and don’t listen to anyone including me (:&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Notes on Contentment</title>
      <link>https://nathanherald.com/posts/legacy/notes-on-contentment/</link>
      <pubDate>Sun, 10 Aug 2014 20:39:48 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/notes-on-contentment/</guid>
      <description>&lt;p&gt;Recently, I’ve been concerned about how non-contented I am and how selfishly I act. I’ve been angry and entitled and it’s not something I enjoy. People fall out of love and similarly they fall into discontent. The choice is to work toward contentedness or toward anger, greed, or any number of alternate paths. Being content does not mean one is satisfied, but instead that one is not unhappy in the presence of unsatisfying circumstances.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“I’m not “happy” but I’m not unhappy about it.”&lt;/strong&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;― Alan Bennett, The History Boys&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A content artist is not one who has created all they ever will, but one that is creating as much as necessary. Being content does not mean a person is complacent or indifferent. Contentedness will breed progress or support to perpetuate itself and prevent complacency. Mediocrity will never grow contentedness.&lt;/p&gt;
&lt;p&gt;Similarly, contentedness is not earned. Only through the removal of dissatisfaction and other poisonous feelings can one be content; not from adding extra income, extra time, etc. Unreachable goals are poison to a person’s contentment. Yet, even the goal of being content is unreachable without true internal change — so the goal itself can be just as preventative as wishing for infinite wealth or other unattainable (and unnecessary) things. I’ve been stressing about this mentally for a while now.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“He who is not contented with what he has, would not be contented with what he would like to have.”&lt;/strong&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;― Socrates&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Talking to a friend, he reminded me that gratitude is the secret to true contentment. Just as contentment must be practiced or lost, it’s clear that gratitude also must be practiced or forgotten. So I intend to practice: I will write once a day about what I am grateful for. In searching for contentment I’ve found gratitude instead. It’s probable one will cause the other.&lt;/p&gt;
&lt;p&gt;I’m not looking for happiness, that will come and go on its own. Anyway, there is a direct correlation to a person’s extraversion to their happiness: the more extraverted a person behaves the happier they tend to be. Neuroticism correlates as well, except in reverse. I am not extraverted.&lt;/p&gt;
&lt;p&gt;I’m also not really looking for ways to rationalize or “know more” or become enlightened. Unlike enlightenment, there is no level of ascension that provides contentment directly. Enlightenment is attained whereas contentment is worked through.&lt;/p&gt;
&lt;p&gt;Every night I will compile a list of the things I am thankful for with no other goal than to relearn gratitude. You’re welcome to try too.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Document semi-wacky — wacky things</title>
      <link>https://nathanherald.com/posts/legacy/document-semi-wacky-wacky-things/</link>
      <pubDate>Thu, 05 Dec 2013 09:42:53 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/document-semi-wacky-wacky-things/</guid>
      <description>&lt;p&gt;&lt;em&gt;This is an exposition of point 4 from&lt;/em&gt; &lt;a href=&#34;https://nathanherald.com/posts/legacy/be-a-good-programmer/&#34;&gt;&lt;em&gt;Be a good programmer&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;so-when-do-you-document-anything&#34;&gt;So when do you document anything?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;When it’s wacky or super difficult.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Thing’s aren’t always simple. Payment processors, email systems, history tracking, and other things can get really complicated fast and sometimes you just need document how it works. &lt;a href=&#34;http://www.tumblr.com/docs/en/api/v2#auth&#34;&gt;Tumblr’s api&lt;/a&gt; uses some complicated signing (HMAC-SHA1) for requesting an oauth token and &lt;a href=&#34;https://github.com/tumblr/tumblr_client/blob/master/bin/tumblr#L27-L51&#34;&gt;it turns out&lt;/a&gt; it’s not exactly the simplest thing ever and &lt;a href=&#34;https://github.com/myobie/fetcher/blob/master/services/tumblr.rb#L56-L72&#34;&gt;even wackier&lt;/a&gt; if you don’t want to use any external libraries. That really needs to be documented. It’s wacky.&lt;/p&gt;
&lt;p&gt;When integrating with payment systems like paypal or authorize.net or braintree you just gotta do things a certain way and it would help the next person if you documented the order or operations, the libraries used, and the “why?” Asynchronous operations are also notoriously hard to follow, so it’s usually a good idea to put some comments in about when and where things might happen. Job queues, notifications, or feedback loops are all candidates for documenting more than normal.&lt;/p&gt;
&lt;h3 id=&#34;when-should-you-not-documentthings&#34;&gt;When should you not document things?&lt;/h3&gt;
&lt;p&gt;Pretty much the rest of the time.&lt;/p&gt;
&lt;p&gt;Code &lt;em&gt;should&lt;/em&gt; speak for itself. Of course. If a programmer’s job is done well, then anyone can get up to speed on what’s going on quickly without having to read too many docs.&lt;/p&gt;
&lt;p&gt;Docs that are useless include things like @public or @private or listing out the types for the args for untyped languages (foo(String:bar)). Code examples showing the use of the methods are usually not a good idea because they get out of sync. &lt;strong&gt;If the method is being used in the project, then it’s example usage is already documented.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A method has three chances to document itself:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Using an appropriate name&lt;/li&gt;
&lt;li&gt;Not being too long&lt;/li&gt;
&lt;li&gt;Being consistently used throughout the application&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;use-an-appropriate-name&#34;&gt;Use an appropriate name&lt;/h4&gt;
&lt;p&gt;If a method sends an email, name it as such. But don’t hide indirect side effects. Recently at work we noticed a method named find_by_identifier that actually will either find something, create something, and also possibly send an email. It didn’t start out that way, but as the product evolved it gained new responsibilities.&lt;/p&gt;
&lt;p&gt;That does not mean the method should have been refactored — refactoring is to change the method’s instructions without changing behavior. Instead, it needs to be re-specified and re-implemented. It’s a &lt;strong&gt;new&lt;/strong&gt; thing and deserves that kind of consideration.&lt;/p&gt;
&lt;h4 id=&#34;dont-be-longer-than-a-fewlines&#34;&gt;Don’t be longer than a few lines&lt;/h4&gt;
&lt;p&gt;The more things a method does, the harder it is to build a mental model of the product or feature one is working on. Having small methods makes refactoring easier and causes less test breakage in the long run. Specify, implement, and refactor until each method is understandable and small.&lt;/p&gt;
&lt;p&gt;Don’t worry, you’re not writing a timer for the nuclear missiles. Or, if you are, you shouldn’t listen to me. Seriously.&lt;/p&gt;
&lt;h4 id=&#34;be-consistent&#34;&gt;Be consistent&lt;/h4&gt;
&lt;p&gt;If you create a &lt;code&gt;find_by_email&lt;/code&gt; method and usually use it like &lt;code&gt;find_by_email(params[:email])&lt;/code&gt; but then one time you pass in some strange object like &lt;code&gt;find_by_email(Email.first)&lt;/code&gt; then I am now confused. I thought &lt;code&gt;params[:email]&lt;/code&gt; was always a String, but now I don’t know. I gotta go digging to learn more to figure out what’s going on.&lt;/p&gt;
&lt;p&gt;Do you really need to support both String and Email objects? &lt;strong&gt;Probably not.&lt;/strong&gt; Do &lt;code&gt;find_by_email(Email.first.address)&lt;/code&gt; or &lt;code&gt;find_by_email(Email.first.to_s)&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;ok-ok-how-do-i-document-thisstuff&#34;&gt;OK. OK. How do I document this stuff?&lt;/h3&gt;
&lt;h4 id=&#34;be-succinct&#34;&gt;Be succinct&lt;/h4&gt;
&lt;p&gt;When you do need to document, it’s best to keep the documentation as close to the actual code as possible — so that means comments. I really like &lt;a href=&#34;http://tomdoc.org/&#34;&gt;TomDoc&lt;/a&gt; for backend application code It’s quick and simple and helps keep everything consistent.&lt;/p&gt;
&lt;p&gt;For javascript I really love &lt;a href=&#34;http://jashkenas.github.io/docco/&#34;&gt;docco&lt;/a&gt;. It’s beautiful and inline. I’m considering moving almost all of my docs to this style.&lt;/p&gt;
&lt;h4 id=&#34;and-libraries&#34;&gt;And Libraries&lt;/h4&gt;
&lt;p&gt;Take the extra step to outline regular usage examples, lists of classes and methods, and maybe answer some frequently asked questions. Since multiple random people might be using the code it needs to be really well explained.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.sinatrarb.com/documentation.html&#34;&gt;One of my favorite projects&lt;/a&gt; has pretty decent documentation. And &lt;a href=&#34;http://guides.rubyonrails.org/&#34;&gt;Rails’s guides&lt;/a&gt; are pretty great, but the &lt;a href=&#34;http://api.rubyonrails.org/&#34;&gt;regular api docs&lt;/a&gt; are pretty sad. Sure they list out the methods and everything, but some are missing or just have no documentation at all. This is something &lt;em&gt;we&lt;/em&gt; can fix.&lt;/p&gt;
&lt;p&gt;I need to know how to install, configure, and do basic tasks with your tool quickly. &lt;strong&gt;Put all of that in the README.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;anything-else&#34;&gt;Anything else?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What did I miss?&lt;/strong&gt; This is the end of the four part series. What did I leave out or what could I do to make it better?&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Use technologies the rest of your team is motivated to use</title>
      <link>https://nathanherald.com/posts/legacy/use-technologies-the-rest-of-your-team-is-motivated-to-use/</link>
      <pubDate>Sun, 28 Jul 2013 15:56:02 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/use-technologies-the-rest-of-your-team-is-motivated-to-use/</guid>
      <description>&lt;figure&gt;
  &lt;a href=&#34;https://flickr.com/photos/kenfagerdotcom&#34;&gt;
  &lt;img
    alt=&#34;Sticky notes with stick figure drawings reading Project Success, Communication, Teamwork, and Achievement&#34;
    width=&#34;1200&#34;
    height=&#34;920&#34;
    src=&#34;https://nathanherald.com/posts/legacy/use-technologies-the-rest-of-your-team-is-motivated-to-use/0-jgGS-dUw9YsXZ3lJ_hu6054205129516536416.jpeg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    via &lt;a href=&#34;https://flickr.com/photos/kenfagerdotcom&#34;&gt;flickr.com/photos/kenfagerdotcom&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;em&gt;This is an exposition of point 3 from&lt;/em&gt; &lt;a href=&#34;https://nathanherald.com/posts/legacy/be-a-good-programmer/&#34;&gt;&lt;em&gt;Be a good programmer&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Most of my time is spent either reading or editing someone else’s work. Very seldom does one get to start from scratch when building something for the web – and even then we use frameworks and libraries we didn’t write. It’s very important that the code you choose to include in your projects is compatible with your colleague’s interests and motivations.&lt;/p&gt;
&lt;p&gt;You may want to write a new app in go, but no one else does. Then don’t do it. Write a personal app in go or write the app in go, but don’t show them and rewrite it in whatever. &lt;strong&gt;It’s important to leave a legacy of making things easier.&lt;/strong&gt; Someone in the future will curse your name during a git blame if you’re not careful. It might even be you.&lt;/p&gt;
&lt;p&gt;Most of the developers I work with use ruby, rails, mysql or postgres, redis, memcached, git, etc all the time. Now, I’m pretty fortunate, because those are all awesome things to use. However, I learned this lesson more than once working at Livingsocial: &lt;strong&gt;don’t go against the grain unless it’s worth the friction.&lt;/strong&gt; (It might be worth it, but make for certain.)&lt;/p&gt;
&lt;p&gt;When I was planning out a new set of apis for their iOS app I had planned to use sinatra and redis. At the time, no team had needed a large redis install, so there wasn’t a primary+secondary pair readily available. &lt;em&gt;The ops guy told me no.&lt;/em&gt; He said we hire rails people and we use mysql. I was pretty miffed at the time, I felt like I was being kept from doing my best work by being limited.&lt;/p&gt;
&lt;p&gt;Sinatra and rails are both ruby, how different could they be? How hard could it be to install and maintain an HA redis setup?&lt;/p&gt;
&lt;p&gt;It turns out the answers are: &lt;em&gt;quite different&lt;/em&gt; and &lt;em&gt;very hard&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;When building a core service or library that will be used and modified by any number of people, it’s usually best to be a little boring about it. Sure, go ahead and prototype something in closure. But when it comes time to run something in production that serves millions of users, &lt;strong&gt;boring trumps clever almost every time.&lt;/strong&gt; When you are away and no one else even knows how to run the tests in the app – well, that’s when they curse your name.&lt;/p&gt;
&lt;p&gt;I’m not advocating that we never push things forward or try out new things. Instead, rally the troops and get everyone involved. If everyone on the team is excited to learn something new, then it won’t matter if you are there or not – anyone can jump in and take care of business. You will learn something new way faster if your colleagues are learning it too. They will discover things you didn’t, you can argue about best practices, and bounce things around so they end up the best they can be.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Refactor only when you have some sort of tests</title>
      <link>https://nathanherald.com/posts/legacy/refactor-only-when-you-have-some-sort-of-tests/</link>
      <pubDate>Thu, 11 Jul 2013 14:22:50 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/refactor-only-when-you-have-some-sort-of-tests/</guid>
      <description>&lt;figure&gt;
  &lt;img
    alt=&#34;John Deer tracker&#34;
    width=&#34;1024&#34;
    height=&#34;684&#34;
    src=&#34;https://nathanherald.com/posts/legacy/refactor-only-when-you-have-some-sort-of-tests/0-UhlBcENy99gkZ-dt_hu5793157229902638462.jpeg&#34;&gt;
  &lt;figcaption&gt;
    via Odalaigh on flickr.com
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;em&gt;This is an exposition of point 2 from&lt;/em&gt; &lt;a href=&#34;https://nathanherald.com/posts/legacy/be-a-good-programmer/&#34;&gt;&lt;em&gt;Be a good programmer&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Most of my time as a programmer is spent reading and refactoring code. Very seldom am I just typing out lines and lines of new code - and even when I am, I usually end up refactoring that new code before it’s shipped out.&lt;/p&gt;
&lt;p&gt;By definition, you cannot refactor code without some sort of tests. That’s what refactoring is, changing code without changing the end result of it’s work.&lt;/p&gt;
&lt;p&gt;A simple example you probably think is stupid is CSS. Change some styles, refresh the page. The refresh is the test. You, the human, are verifying that you didn’t change the way things look. This even applies to developers refactoring scripts or application code, sometimes a human refreshing is how we know we didn’t change anything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There are some key points to know:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;If a test has always passed, if it never failed in the beginning, then you don’t know if it’s testing anything&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If a test fails during a refactoring, it was too granular&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Your tests only try out your assumptions of how the system works&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;tests-gottafail&#34;&gt;Tests gotta fail&lt;/h3&gt;
&lt;p&gt;Writing tests first is good, because you know they will fail, then pass, then you are good. I don’t always do this, but it’s probably the best idea. TDD as they call it.&lt;/p&gt;
&lt;p&gt;If you write some code, then write a passing test for it, you don’t know anything from the test. Nothing.&lt;/p&gt;
&lt;p&gt;An example in ruby that I see often is using &lt;code&gt;assert&lt;/code&gt; instead of &lt;code&gt;assert_equal&lt;/code&gt;. Like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;assert &amp;#34;foo&amp;#34;, some_var 
assert_equal &amp;#34;foo&amp;#34;, some_var
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first assert will always pass because &lt;code&gt;&amp;quot;foo&amp;quot;&lt;/code&gt; is truthy. &lt;code&gt;some_var&lt;/code&gt; is actually being given as the &lt;code&gt;&amp;quot;message&amp;quot;&lt;/code&gt; argument, which is shown if the assert fails.&lt;/p&gt;
&lt;p&gt;If you require a test to fail before it succeeds, this type of mistake wouldn’t happen.&lt;/p&gt;
&lt;h3 id=&#34;tests-need-to-bestable&#34;&gt;Tests need to be stable&lt;/h3&gt;
&lt;p&gt;Changing one line of code shouldn’t break a ton of tests. If it does, your tests are probably too granular to mean much. Trying to assert every little thing about a system is dangerous, because it can lead to only testing small interactions, but never knowing if the entire system even works. I’ve seen rails apps with decent coverage and all tests passing that raise errors all the time.&lt;/p&gt;
&lt;p&gt;Try to think about the ins and outs of the system, not the actual algorithms going on inside.&lt;/p&gt;
&lt;h3 id=&#34;your-tests-areflawed&#34;&gt;Your tests are flawed&lt;/h3&gt;
&lt;p&gt;Yeah, it kinda sucks, but your tests are not adequate. The problem is that you only test how you think the system works. It’s ok though, you can’t really help it.&lt;/p&gt;
&lt;p&gt;Applications or websites or whatever act in ways that we would never imagine or predict. It’s pretty cool to see someone use a tool you made for things you never thought of. It also means you didn’t write a test for that scenario.&lt;/p&gt;
&lt;p&gt;Honestly, you don’t need to test every scenario. But, if users get used to how something works, even if it’s working in a broken way, they will complain and possibly dissent when it changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The solution:&lt;/strong&gt; have someone else look at it and/or use it.&lt;/p&gt;
&lt;p&gt;Most problems we work on &lt;strong&gt;are human problems&lt;/strong&gt; anyway, the computer parts of things is most times figured out. I mean unless your Facebook and you have a bajillion users so you write your own php. Then you are solving problems no one else has. Then it’s whatever. However, more than likely, you are not Facebook.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Change as little as possible to fix a problem</title>
      <link>https://nathanherald.com/posts/legacy/change-as-little-as-possible-to-fix-a-problem/</link>
      <pubDate>Mon, 03 Jun 2013 15:00:42 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/change-as-little-as-possible-to-fix-a-problem/</guid>
      <description>&lt;figure&gt;
  &lt;a href=&#34;https://flickr.com/photos/acme&#34;&gt;
  &lt;img
    alt=&#34;Photo of clouds&#34;
    width=&#34;1200&#34;
    height=&#34;900&#34;
    src=&#34;https://nathanherald.com/posts/legacy/change-as-little-as-possible-to-fix-a-problem/0-NX88j1cl5kDz0wbK_hu6239650882487542560.jpeg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    &lt;em&gt;via&lt;/em&gt; &lt;a href=&#34;https://flickr.com/photos/acme&#34;&gt;&lt;em&gt;flickr.com/photos/acme&lt;/em&gt;&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;em&gt;This is an exposition of point 1 from&lt;/em&gt; &lt;a href=&#34;https://nathanherald.com/posts/legacy/be-a-good-programmer/&#34;&gt;&lt;em&gt;Be a good programmer&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When a problem arises or a bug is found, it can sometimes be very difficult to determine the exact cause. We have to look through many files and read lots of code to find one line or paragraph of code that is causing the larger issue. It’s tempting to fix other issues as you read through files and classes or what have you. Indenting or reformatting code, renaming arguments, unwrapping map reduce’s into separate methods, and changing comments on unrelated code are fine, just separate from fixing the bug.&lt;/p&gt;
&lt;p&gt;A good programmer knows that they should only commit what is needed to fix the bug. It creates a clean history of changes, where this change is easily explained in its entirety. Also, if the need arises to revert the change, one is not also reverting unrelated code. When unrelated code is reverted, sometimes it never makes it back in a separate commit. Fixing a problem can be frustrating and cause one to forget the other little things to go back and include later. &lt;/p&gt;
&lt;p&gt;There is also the problem of introducing more bugs while fixing the original, which is far more likely if extra changes are included. Multitasking is hard. To be the most effective at curing an application’s ailments focus on finding and solving one problem at a time. If you &lt;a href=&#34;http://en.wikibooks.org/wiki/Computer_Programming_Principles/Maintaining/Debugging#Change_one_thing_at_a_time&#34;&gt;change one thing a time&lt;/a&gt;, then it’s much less likely that other, seemingly unrelated, components will be effected. &lt;/p&gt;
&lt;p&gt;If you make changes trying to fix an issue, but the end result is still broken, then revert. &lt;a href=&#34;http://en.wikibooks.org/wiki/Computer_Programming_Principles/Maintaining/Debugging#Back_out_changes_that_have_no_effect&#34;&gt;Do not keep unrelated changes&lt;/a&gt; until the problem is solved. Unless you have decided to not solve it anymore - which sometimes is a good answer. Not every problem needs to be fixed immediately. Some bugs require research or timing or other humans to be involved, so don’t go committing things until you have the resources necessary to come up with a full fix. &lt;/p&gt;
&lt;p&gt;Experienced developers mentally give different bugs different priorities, but those can sometimes be incorrectly weighted. Maybe a problem effects an api that you never use or you can continue with your daily work, but someone else cannot. I’ve both caused problems for others as well as been blocked on something. The more power you have to fix something, the less likely you are to ever be blocked. &lt;/p&gt;
&lt;p&gt;Any exception is a bad exception. Don&amp;rsquo;t let bugs keep happening, even if they seem inconsequential. They could mask other, real problems, they are taxing on the application since exceptions are usually slower than the normal code path (especially if you send emails or post to an error reporting service), and they are eventually demotivating. Let exceptions be exceptional. &lt;/p&gt;
&lt;p&gt;If you find yourself demotivated to fix issues, then try to setup a reward system or a dedicated time. Have cleanup Friday’s or bug fix Monday Morning’s. Schedule time once a day to fix the most common bugs. You’ll be much more motivated to fix things once a few fixes have gone in, especially if you receive positive feedback from customers.&lt;/p&gt;

        
      </description>
    </item>
    <item>
      <title>Be a good programmer</title>
      <link>https://nathanherald.com/posts/legacy/be-a-good-programmer/</link>
      <pubDate>Wed, 29 May 2013 00:07:37 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/be-a-good-programmer/</guid>
      <description>&lt;figure&gt;
  &lt;a href=&#34;https://flickr.com/photos/acme&#34;&gt;
  &lt;img
    alt=&#34;Abstract representation of source code on a screen&#34;
    width=&#34;1200&#34;
    height=&#34;900&#34;
    src=&#34;https://nathanherald.com/posts/legacy/be-a-good-programmer/0-V96_4PPAcT3IZ5Dx_hu12267610732218919877.jpeg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    via &lt;a href=&#34;https://flickr.com/photos/acme&#34;&gt;flickr.com/photos/acme&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;ol&gt;
&lt;li&gt;Change as little as possible to fix a problem&lt;/li&gt;
&lt;li&gt;Refactor only when you have some sort of tests&lt;/li&gt;
&lt;li&gt;Use technologies the rest of your team is motivated to use&lt;/li&gt;
&lt;li&gt;Document semi-crazy – crazy things&lt;/li&gt;
&lt;/ol&gt;

        
      </description>
    </item>
    <item>
      <title>The Cost of Education</title>
      <link>https://nathanherald.com/posts/legacy/the-cost-of-education/</link>
      <pubDate>Wed, 08 May 2013 01:39:40 +0000</pubDate>
      
      <guid isPermaLink="true">https://nathanherald.com/posts/legacy/the-cost-of-education/</guid>
      <description>&lt;figure&gt;
  &lt;a href=&#34;https://flickr.com/photos/deathtogutenberg&#34;&gt;
  &lt;img
    alt=&#34;Book reading ‘Steal like an artist’&#34;
    width=&#34;1200&#34;
    height=&#34;900&#34;
    src=&#34;https://nathanherald.com/posts/legacy/the-cost-of-education/0-BlrMdL2vRuiPNxGk_hu16155794054656830946.jpeg&#34;&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Photo from &lt;a href=&#34;https://flickr.com/photos/deathtogutenberg&#34;&gt;flickr.com/photos/deathtogutenberg&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I’ve written this story many times, but never published it. Today, however, Joshua Davis tweeted my exact feelings:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;without piracy, I never would have mastered the tools, that created a career, where I could then purchase said tools in the future.&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;&lt;em&gt;via&lt;/em&gt; &lt;a href=&#34;https://twitter.com/JoshuaDavis/status/331787124326166528&#34;&gt;&lt;em&gt;twitter.com/joshuadavis&lt;/em&gt;&lt;/a&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;This is in response to Adobe switching from a one-time fee to pay for their software to a monthly fee to rent their software. I’ve been paying monthly for Creative Cloud for over a year now, since I upgraded from CS4 to effectively CS6.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We didn’t have a computer when I was young. The computers at school never offered much more than Wordperfect let alone the opportunity to try to tell the computer what to do. I would read books from the library about batch files (they only had old dos books) and how to do things with computers, but I didn’t know what they meant and I didn’t have a way to try them out.&lt;/p&gt;
&lt;p&gt;Then my older brother got a job and bought a computer. I was told not to use it, but I did anyway when he was away. He had downloaded lots of different applications and seldom used any of them, but I found one that I spent hours and hours using: Flash 3. You see, Flash 3 had the ability to create “scripts” by clicking a + icon and choosing a command or statement. It wasn’t altogether very different than writing a BASIC program, which I had learned from using a cheap vtech toy as a child.&lt;/p&gt;
&lt;p&gt;Then Flash 4 came out and you could do ActionScript. I used to visit praystation, flight404, once upon a forest, and other sites every day to see what they were doing and what I could learn. I couldn’t afford books, but I would read every website I could find. I spent time on the alt.marcomedia.flash newsgroup asking and answering questions.&lt;/p&gt;
&lt;p&gt;I never paid for Flash 4. I never paid for 5 either and by the time I had saved up the money to buy the softare legally MX 2004 had been released and I was in college.&lt;/p&gt;
&lt;p&gt;Flash moved me on to Javascript (which it perverted later with ActionScript 3) and html and css and on and on. While Flash has a terribly steep learning curve, being able to program things on the canvas where you can see it all happen and share that instantly with people was exciting.&lt;/p&gt;
&lt;p&gt;New software like &lt;a href=&#34;http://www.bohemiancoding.com/sketch/&#34;&gt;Sketch&lt;/a&gt; ($24.99) and &lt;a href=&#34;http://www.flyingmeat.com/acorn/&#34;&gt;Acorn&lt;/a&gt; ($29.99) are making it much cheaper for beginning designers to get going, but when you have no money even these applications are out of reach.&lt;/p&gt;
&lt;p&gt;Luckily working with html, css, and javascript is all text and editing text will always be possible with free software. I actually use a program called &lt;a href=&#34;http://www.vim.org/&#34;&gt;vim&lt;/a&gt; that I would never recommend to anyone because of how hard it is to use, but it &lt;em&gt;is&lt;/em&gt; free.&lt;/p&gt;
&lt;p&gt;With libraries like &lt;a href=&#34;http://jonobr1.github.io/two.js/&#34;&gt;two.js&lt;/a&gt; and &lt;a href=&#34;http://www.hypeframework.org/&#34;&gt;HYPE&lt;/a&gt; one can get started learning and creating interactive graphics for free. While there are free tools and libraries, the documentation and learning materials are still lacking for beginners. Having to know how to program just to get something to move is terrible. Flash gave back as much as you put into it, which is why so many designers twisted it’s sad, crapptastic features into beautiful things.&lt;/p&gt;
&lt;p&gt;The current, best backend development tools and frameworks are free: ruby, python, rails, django, postgres, redis, memcached, etc. To become a web developer is a much different path: you only need a computer where you can run a free operating system which comes with free software with which you can program to do stuff for free. All you have to pay for is hosting - and with heroku and others there are free options for that as well.&lt;/p&gt;
&lt;p&gt;I’m not sure if design can or should be dominated by free tools, but I do know that if it weren’t for &lt;em&gt;stolen&lt;/em&gt; tools I wouldn’t be where I am today. I was allowed time to master tools I couldn’t afford so I could then make the money to not only buy those tools, but support typeface and icon designers and indie software developers and so on…&lt;/p&gt;
&lt;p&gt;I understand that hammers and nails costs money too, but I can borrow a hammer from a friend while I learn how to use it.&lt;/p&gt;

        
      </description>
    </item>
  </channel>
</rss>
