<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" >
  <generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator>
  <link href="https://henry.catalinismith.se/feed.xml" rel="self" type="application/atom+xml" />
  <link href="https://henry.catalinismith.se/" rel="alternate" type="text/html" />
  <updated>2026-05-12T18:46:10+02:00</updated>
  <id>https://henry.catalinismith.se/feed.xml</id>

  
  
  

  
    <title type="html">Henry Catalini Smith | </title>
  

  
    <subtitle>Software engineer based in Malmö, Sweden.</subtitle>
  

  

  
  
  
  
    <entry>
      

      <title type="html">Joining Forgejo’s Accessibility Team</title>
      <link href="https://henry.catalinismith.se/2026/04/30/joining-forgejos-accessibility-team/" rel="alternate" type="text/html" title="Joining Forgejo’s Accessibility Team" />
      <published>2026-04-30T00:00:00+02:00</published>
      <updated>2026-04-30T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2026/04/30/joining-forgejos-accessibility-team</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/04/30/joining-forgejos-accessibility-team/"><![CDATA[<p>If you go and look at <a href="https://codeberg.org/forgejo/governance/src/branch/main/TEAMS.md#accessibility">TEAMS.md#accessibility</a> in Forgejo’s governance repo today you’ll find my name there in the list. I’m very proud of this!</p>

<p>Materially speaking, it’s minor stuff. There’s no money or power or anything like associated with it. Really it just means I did some accessibility work that was decent enough for the core team to be okay with putting my name in that list, I think partly as a bit of recognition and partly to help keep track of what capabilities the project has to hand at any given moment.</p>

<p>I wrote this in the background section of <a href="https://codeberg.org/forgejo/governance/issues/393">my membership request</a>.</p>

<blockquote>
  <p>I see big opportunities ahead for Forgejo and Codeberg, and I know that WCAG compliance can be a sticking point for closing certain kinds of deal due to procurement legislation such as EN 301 549, so my hope is that any “problems” in this space are actually an opportunity for me to do lots of the kind of work I find most fun, and also contribute to the success of a a project I care a lot about.</p>
</blockquote>

<p>Since <a href="/2026/04/15/lessons-learned-from-the-forgejo-v15-release-blocker/">my adventures with the v15 release</a> a couple of weeks ago, we’ve successfully landed a <a href="https://codeberg.org/forgejo/forgejo/issues/11116">fix</a> for the <a href="https://codeberg.org/forgejo/forgejo/issues/11116">repo file list table semantics issue</a>.</p>

<p>That one is a really big deal: the “latest commit” row used to live in the <code class="language-plaintext highlighter-rouge">&lt;thead&gt;</code> element. As a result, screen readers would announce the repo’s latest commit over and over and over again while navigating the list of files. It was pretty disorienting and it’s great that it’s fixed.</p>

<p>Next up is an upstream improvement for GitHub’s <a href="https://github.com/github/combobox-nav"><code class="language-plaintext highlighter-rouge">@github/combobox-nav</code></a> package. Forgejo had an <a href="https://codeberg.org/forgejo/forgejo/pulls/11860">inaccessible combobox</a> that turned out to be the result of not assigning unique IDs to options. This felt like a mistake that could be handled more gracefully, so I sent a few ideas upstream, and now someone at GitHub is on board with <a href="https://github.com/github/combobox-nav/pull/99">one of them</a>. So hopefully together we’ll eliminate a whole category of accessibility bug that can arise when using that library, which is great news not only for Forgejo but for anyone else using it. Cool to see a nice example of collaboration among forge projects too.</p>

<p>After that, who knows. There’s plenty of interesting bugs to fix, for sure. One I’m particularly excited about is <a href="https://codeberg.org/forgejo/forgejo/issues/1627">making the heatmap keyboard- and screen reader-accessible</a>. I’m also trying to build a new habit of skimming through open PRs and issues looking for places where accessibility feedback can be a positive contribution.</p>

<p>I’m keen to do a little bit of auditing and see what bugs I can file myself, but that’s tricky before becoming more deeply familiar with the existing open issues on the issue tracker and the team’s less formal set of colloquially known issues. Besides, Forgejo has this great combination of factors of both being open source and having a critical mass of users, which means accessibility feedback tends to flow in directly as issues. So it doesn’t feel so urgent to actively add to that list given that bug reports from real-world use are inherently such a strong signal of priority and impact.</p>

<p>Always very rewarding to slowly acclimatise to a project like this outside of an employment context. A lovely thing about open source work is that it’s a context that can outlast individual jobs if you want it to. My <a href="/zetkin/">Zetkin work</a> has outlived two or three jobs by now and I’m optimistic that Forgejo can be a similar deal.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="accessibility" />
      
        <category term="blog" />
      
        <category term="codeberg" />
      
        <category term="forgejo" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[If you go and look at TEAMS.md#accessibility in Forgejo’s governance repo today you’ll find my name there in the list. I’m very proud of this!]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Next.js can’t handle Windows newlines in meta descriptions</title>
      <link href="https://henry.catalinismith.se/2026/04/28/nextjs-cant-handle-windows-newlines-in-meta-descriptions/" rel="alternate" type="text/html" title="Next.js can’t handle Windows newlines in meta descriptions" />
      <published>2026-04-28T00:00:00+02:00</published>
      <updated>2026-04-28T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2026/04/28/nextjs-cant-handle-windows-newlines-in-meta-descriptions</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/04/28/nextjs-cant-handle-windows-newlines-in-meta-descriptions/"><![CDATA[<p>Meta descriptions containing Windows-style <code class="language-plaintext highlighter-rouge">\r\n</code> newlines are broken in Next.js. When <code class="language-plaintext highlighter-rouge">\r</code> is present in a meta description, Next.js will render two <code class="language-plaintext highlighter-rouge">&lt;meta name="description"&gt;</code> tags instead of one, and each with slightly different content.</p>

<p>Reproducing this bug is fairly simple.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">generateMetadata</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">{</span>
    <span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Hello, World!</span><span class="se">\r\n</span><span class="s2">Hello, World!</span><span class="dl">"</span><span class="p">,</span>
  <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p>After building the site, you’ll see one meta description in the built HTML, as expected. Next strips the carriage return, which is why we only see <code class="language-plaintext highlighter-rouge">$</code> instead of <code class="language-plaintext highlighter-rouge">^M$</code> in the output below.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl -s http://localhost:3002/ | htmlq "meta[name='description']" | cat -vet
&lt;meta content="Hello, World!$
Hello, World!" name="description"&gt;$
</code></pre></div></div>

<p>But in the RSC payload, the carriage return is still there.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat .next/server/app/index.rsc | grep -o '"name":"description","content":"[^"]*'
"name":"description","content":"Hello, World!\r\nHello, World!
</code></pre></div></div>

<p>When the page loads, Next compares the meta tags in the HTML with the ones in the RSC payload. It notices that this one with the carriage return in it is “missing”, and adds it.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; [...document.querySelectorAll('meta[name="description"]')].map(e =&gt; e.getAttribute('content'))
&lt; [
  'Hello, World!\nHello, World!',
  'Hello, World!\r\nHello, World!'
]
</code></pre></div></div>

<p>If you have a Next.js site hooked up to a CMS and the people using the CMS are on Windows, then there’s a good chance you have pages affected by this bug. It might take Vercel a while to get around to fixing <a href="https://github.com/vercel/next.js/issues/93089">the problem</a>, but it’s easy enough to run your metadata through <code class="language-plaintext highlighter-rouge">.replace(/\r/g, '')</code> to avoid it in the meantime.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[Meta descriptions containing Windows-style \r\n newlines are broken in Next.js. When \r is present in a meta description, Next.js will render two &lt;meta name="description"&gt; tags instead of one, and each with slightly different content.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Returning to Jekyll</title>
      <link href="https://henry.catalinismith.se/2026/04/16/returning-to-jekyll/" rel="alternate" type="text/html" title="Returning to Jekyll" />
      <published>2026-04-16T00:00:00+02:00</published>
      <updated>2026-04-16T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2026/04/16/returning-to-jekyll</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/04/16/returning-to-jekyll/"><![CDATA[<p>Tech choices for personal websites can feel very personal. For much of the 2010s, <a href="https://jekyllrb.com/">Jekyll</a> was what I was using. It had come out of GitHub, and GitHub was cool so Jekyll was cool. Then at some point, <a href="https://www.11ty.dev/">Eleventy</a> came along. Netlify were spending a lot of money to make “Jamstack” happen, and Jamstack was cool so Eleventy was cool.</p>

<p>Being as much a slave to trends as anyone else in tech, I rode that hype train from Jekyll to Eleventy. And times were good. You got all the vibrance and bustle of the npm ecosystem with its tailwinds and its rehypes, and you never had to gaze in terror upon the dreaded words “Installing nokogiri with native extensions” again.</p>

<p>Then Eleventy got acquired. And fair enough, I guess. People gotta eat. And they’re renaming it Build Awesome. Bit of a jarring shift in tone there if you ask me. Very loud and American all of a sudden.</p>

<figure>
 <img alt="Movie still with three storey tall Lego townhouses on the left and large skyscrapers on the right, with wrecking balls in the centre about to destroy the old townhouses" src="/2026/04/16/awesome@1024x427.webp" />
 <figcaption>
  In the middle of the "Everything is Awesome" song in the Lego Movie there's this scene where a guy is like "Okay it says here to take everything weird and blow it up!" right before they demolish a bunch of quirky old townhouses.
 </figcaption>
</figure>

<p>Like I said, tech choices for personal websites can feel very personal. And whoever this new thing is for, it doesn’t <em>feel</em> like it’s for me . So I looked around and figured out that a few other people felt the same, and that the next stop for the hype train appears to be <a href="https://astro.build/">Astro</a>. But Astro’s just been <a href="https://blog.cloudflare.com/astro-joins-cloudflare/">acquired by Cloudflare</a> and has the kind of landing page that feels like it’s only missing a “Contact Sales” button. So maybe not.</p>

<p>I came to the conclusion that it was worth reevaluating Jekyll, and I was pleasantly surprised. Its website hasn’t changed in ten years and neither has anything about how it works. Porting the site back was painless, and to my amazement, sped up my builds significantly. In the whole of 2026 so far, Jekyll has had 12 commits. Because Jekyll is <em>finished</em>. It’s complete. It’s done. And perhaps most importantly in a world that’s become too exciting for its own good, Jekyll is <a href="https://boringtechnology.club/"><em>boring</em></a>. Ten years from now it will probably still exist, still be called Jekyll, and still work the same as it did in 2016.</p>

<p>So hurray for boring things! And good luck to the Font Awesome team with Build Awesome Pro while we’re at it. Here’s hoping it’s a huge hit despite my own lack of interest, and everyone involved becomes unimaginably wealthy after it inevitably gets acquired by Automattic or something!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[Tech choices for personal websites can feel very personal. For much of the 2010s, Jekyll was what I was using. It had come out of GitHub, and GitHub was cool so Jekyll was cool. Then at some point, Eleventy came along. Netlify were spending a lot of money to make “Jamstack” happen, and Jamstack was cool so Eleventy was cool.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/04/16/awesome@1024x427.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/04/16/awesome@1024x427.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Lessons learned from the Forgejo v15 release blocker</title>
      <link href="https://henry.catalinismith.se/2026/04/15/lessons-learned-from-the-forgejo-v15-release-blocker/" rel="alternate" type="text/html" title="Lessons learned from the Forgejo v15 release blocker" />
      <published>2026-04-15T00:00:00+02:00</published>
      <updated>2026-04-15T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2026/04/15/lessons-learned-from-the-forgejo-v15-release-blocker</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/04/15/lessons-learned-from-the-forgejo-v15-release-blocker/"><![CDATA[<p>A <a href="https://codeberg.org/forgejo/forgejo/pulls/11846">pull request of mine</a> recently needed to be <a href="https://codeberg.org/forgejo/forgejo/pulls/12088">reverted</a> due to a <a href="https://codeberg.org/forgejo/forgejo/issues/12082">visual bug</a> in order to unblock the release of Forgejo version 15. This was about 10% embarrassing, 10% frustrating, and 80% really really interesting. I learned a whole bunch about Forgejo’s code and release process, as well as my own biases and focus areas to become a better contributor. It would be a good problem to have if more people like me came along to contribute and made mistakes like these. I figured I’d write down what I learned so that it’s easy to pay it forward.</p>

<p>Open source release cycles are so different to typical private sector release cycles. It’s been more than ten years since I last worked somewhere where something like backporting fixes to older releases was a concern. Every job since then has been somewhere between continuous deployment and biweekly releases. You can afford to have more of a push-and-pray attitude to quality in that kind of environment, since mistakes can be mopped up in the next release. I hadn’t noticed how much my habits had been shaped by that context until seeing them fail in this one.</p>

<p>Another bias it helped me to notice is related to tools. The dominance of components as the primary front end building block in recent years has taught me to think of each UI element as its own self-contained little world. I’d updated the HTML template and the most relevant-looking associated CSS so the job <em>felt</em> very done at some subconscious level. The idea that other layers of further-away CSS might also be in play simply did not occur to me due to this component-centric mindset.</p>

<p>A third bias I discovered was that I had a little too much faith in my own process. For example, I’m utterly meticulous about providing “before” &amp; “after” screen recording demos for fixes like these. The act of producing these videos is a key moment in my process. Because it requires distilling the change down into a bitesize comparison and capturing it visually, it’s the moment where I catch most of my “oh shit, I nearly missed that!” bugs. In this case, due to the bug being in a visual detail I wasn’t trying to change and relating to CSS code that wasn’t in the diff, I missed this one anyway even though it’s clearly visible in <a href="https://codeberg.org/attachments/73375292-d749-4e5c-84e1-203b6b56b6f5">the “after” video on the pull request</a>.</p>

<p>Something that went very well here, though, was the fast turnaround on the revert pull request. In the regression issue thread I estimated that fixing it would take several days and offered to revert the original PR instead to unblock the release. I’m glad we went with that option. My stress level was immediately back at baseline once I’d created the revert pull request.</p>

<p>Another positive was that the new perspective resulting from the mistake made me understand how overextended and sloppy I’d become. The <a href="https://codeberg.org/forgejo/forgejo/pulls/11953#issuecomment-13026201">same mistake</a> was present in another open pull request of mine, I learned. So I closed it, along with a few others, to give myself some thinking time and free up maintainer resources to work on the release instead.</p>

<p>That thinking time went more or less like this: I feel utterly determined to continue contributing to this project, but also some embarrassment and frustration about the level of quality of some of my work so far. How can I tweak my approach in order to get those levels somewhere satisfactory despite being quite unfamiliar with the code still?</p>

<p>One technique I love for catching unexpected visual differences between two versions of a webpage is to open the old version and the new one in adjacent tabs and rapidly jump back and forth between the two tabs. The speed is key, because it gives the differences an almost animation-like quality and makes them jump out of the screen at you. I decided to figure out a workflow to bring this check into my process.</p>

<p>What I came up with involves checking out the <code class="language-plaintext highlighter-rouge">forgejo</code> (i.e. main) branch, building and running the application and opening a tab showing the feature being worked on, then switching branches and repeating the process there. At that point you have a “before” tab and an “after” tab and can use the rapid tab switching technique to surface any unexpected changes.</p>

<p>Here’s a demo! I run each command in sequence on the left, and then at around 00:36 when both tabs are ready I begin switching back and forth between them. The visual bug jumps out at you very clearly.</p>

<figure>
 <video src="/2026/04/15/demo@1024x620.mp4" poster="/2026/04/15/demo@1024x620.webp" controls="" preload="none" playsinline="">
 </video>
</figure>

<p>Given that structural HTML changes are such a key part of accessibility retrofitting and that Forgejo’s approach to selectors couples its CSS quite tightly to the structure of the HTML, I think this technique is going to be critically important to be able to deliver the necessary amount of retrofitting work without these kinds of regressions. And hopefully it can be of use to others who show up to help in the future too!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="accessibility" />
      
        <category term="devtools" />
      
        <category term="forgejo" />
      

      
      
        <summary type="html"><![CDATA[A pull request of mine recently needed to be reverted due to a visual bug in order to unblock the release of Forgejo version 15. This was about 10% embarrassing, 10% frustrating, and 80% really really interesting. I learned a whole bunch about Forgejo’s code and release process, as well as my own biases and focus areas to become a better contributor. It would be a good problem to have if more people like me came along to contribute and made mistakes like these. I figured I’d write down what I learned so that it’s easy to pay it forward.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/04/15/demo@1024x620.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/04/15/demo@1024x620.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Integrating the moon into your terminal</title>
      <link href="https://henry.catalinismith.se/2026/04/06/integrating-the-moon-into-your-terminal/" rel="alternate" type="text/html" title="Integrating the moon into your terminal" />
      <published>2026-04-06T00:00:00+02:00</published>
      <updated>2026-04-06T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2026/04/06/integrating-the-moon-into-your-terminal</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/04/06/integrating-the-moon-into-your-terminal/"><![CDATA[<p>As I write this, a spaceship full of astronauts is flying around the moon for
the first time in my life. They don’t even have a working toilet right now
either so they’re all gasping for a piss while they do it. It’s all very
exciting.</p>

<p>Moon shit is some of the best kind of science IMO. I’m into basically anything
moon-related. A while back I decided even on my computer I’m more interested in
the moon than any of  the stereotypial nerd telemetry like CPU graphs and
whatever. I reckon it’s a healthy grounding thing to rig your computer to
remind you of the passage of time and your tiny place in the universe instead of
focusing your attention on a bunch of meaningless blinkenlights about an
environment under your full control.</p>

<p>So whenever I open a new terminal, I get this little ASCII art thing
that shows me the current phase of the moon.</p>

<figure>
 <img src="/2026/04/06/phoon@2866x1692.webp" alt="Terminal screenshot showing an ASCII art representation of the moon in multiple colours" />
</figure>

<p>Since the moon is cool as fuck right now, let’s see if we can hype some more
people up into making their terminals do this. First up, you need <code class="language-plaintext highlighter-rouge">phoon</code> and
<code class="language-plaintext highlighter-rouge">lolcat</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install phoon lolcat
</code></pre></div></div>

<p>The first one – phoon – prints the ASCII art moon phase. The second one –
lolcat – applies some rainbow colourisation to it. All you have to do to get
that cool rainbow moon as the splash screen for every new terminal you open
for the rest of your life is to pipe phoon into lolcat.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>phoon | lolcat
</code></pre></div></div>

<p>You can chuck that into <code class="language-plaintext highlighter-rouge">~/.zshrc</code> or <code class="language-plaintext highlighter-rouge">~/.bashrc</code> and you’re good to go. I’ve
been using <a href="https://fishshell.com/">fish</a> lately, where this sort of thing
belongs in <a href="https://fishshell.com/docs/current/cmds/fish_greeting.html"><code class="language-plaintext highlighter-rouge">fish_greeting</code></a>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function fish_greeting
  phoon | lolcat
end
</code></pre></div></div>

<p>Highly recommended! While watching tonight’s livestream of the Artemis fly-by, I
noticed that the shadow in the top left of the moon was different from the top
right shadow in the Earth perspective, despite the moon not being visible from
my window in Malmö tonight.</p>

<figure>
 <img src="/2026/04/06/phases@1172x562.webp" alt="Composite image. Left hand side shows a moon with the top right hand side blacked out by shadow. Right hand side shows a moon with the top left blacked out by shadow. The right-hand moon is markedly flatter." />
 <figcaption>Earth perspective on the left, Artemis II perspective on the right</figcaption>
</figure>

<p>This sense of perspective really enhanced the coolness of the livestream. And I
wouldn’t have picked up on this if it hadn’t been for the fact that my computer
reminds me of what’s going on with the moon basically every time I use it.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[As I write this, a spaceship full of astronauts is flying around the moon for the first time in my life. They don’t even have a working toilet right now either so they’re all gasping for a piss while they do it. It’s all very exciting.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/04/06/phoon@2866x1692.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/04/06/phoon@2866x1692.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Accessing localhost:3000 in VirtualBox for NVDA testing on macOS</title>
      <link href="https://henry.catalinismith.se/2026/04/01/accessing-localhost3000-in-virtualbox-for-nvda-testing-on-macos/" rel="alternate" type="text/html" title="Accessing localhost:3000 in VirtualBox for NVDA testing on macOS" />
      <published>2026-04-01T00:00:00+02:00</published>
      <updated>2026-04-01T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2026/04/01/accessing-localhost3000-in-virtualbox-for-nvda-testing-on-macos</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/04/01/accessing-localhost3000-in-virtualbox-for-nvda-testing-on-macos/"><![CDATA[<p>After <a href="/2026/03/25/testing-in-jaws-on-a-mac">getting my JAWS setup in place</a>
recently, NVDA was next on the agenda. It was easy enough to install, which
left me with enough leftover time and energy to make an attempt at loading
<a href="localhost:3000">http://localhost:3000</a> on it in order to be able to test
unmerged changes. It went better than expected!</p>

<p>I found out you can use <a href="10.0.2.2:3000">http://10.0.2.2:3000</a> to access
port 3000 on the host machine. No config needed.</p>

<figure>
 <video controls="" preload="none" poster="/2026/04/01/nvda@1024x724.webp">
  <source src="/2026/04/01/nvda@1024x724.mp4" type="video/mp4" />
 </video>
</figure>

<p>Very cool indeed to be this well-equipped. NVDA is a little bit nicer to use
somehow than JAWS, too.</p>

<p>Plus, since setting up the JAWS thing, I learned that it’s a violation of the
license to use the trial version of JAWS for accessibility testing. So NVDA
seems like it’s going to be my go-to option for Windows screen reader testing.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="accessibility" />
      
        <category term="blog" />
      
        <category term="forgejo" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[After getting my JAWS setup in place recently, NVDA was next on the agenda. It was easy enough to install, which left me with enough leftover time and energy to make an attempt at loading http://localhost:3000 on it in order to be able to test unmerged changes. It went better than expected!]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/04/01/nvda@1024x724.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/04/01/nvda@1024x724.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Inspecting HTML elements that disappear on blur</title>
      <link href="https://henry.catalinismith.se/2026/03/28/inspecting-html-elements-that-disappear-on-blur/" rel="alternate" type="text/html" title="Inspecting HTML elements that disappear on blur" />
      <published>2026-03-28T00:00:00+01:00</published>
      <updated>2026-03-28T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/03/28/inspecting-html-elements-that-disappear-on-blur</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/03/28/inspecting-html-elements-that-disappear-on-blur/"><![CDATA[<p>Web accessibility is mostly about HTML, I reckon. Tons of the bugs you work on
end up being about adding or removing some attribute from an element. So the
inspect element tool is a really important part of the toolkit. And most of the
time you don’t even have to think about it: just ⌘⇧C, click on the element
you’re curious about, and you’re in business.</p>

<p>That simple workflow falls apart when you need to debug an element that
disappears when you blur the document. Stuff like comboboxes and dropdown menus
are built to close themselves when the user is finished with them. So when you
try to expand them in the inspect element tool, they vanish. It’s bloody
frustrating!</p>

<p>Sometimes it’s easy to work around the problem by hardcoding the UI component
to ignore the blur event and remain open. This is most often possible in
projects that contain their own set of base UI components. Today I found myself
dealing with a case where that wasn’t practical, and realised it might be worth
sharing the workaround I always use.</p>

<p>I’d found this really interesting issue about <a href="https://codeberg.org/forgejo/forgejo/issues/7668">the @mention combobox in Forgejo’s
issue comment UI not being announced properly</a>.
Forgejo uses the <code class="language-plaintext highlighter-rouge">@github/text-expander-element</code> for this, and GitHub’s known
for taking accessibility pretty seriously, so I was surprised it didn’t just
work perfectly out of the box and curious about why. Couldn’t use inspect
element though!</p>

<figure>
 <video poster="/2026/03/28/no@1000x544.webp" src="/2026/03/28/no@1000x544.mp4" title="Browser debugging session" controls="" preload="none" playsinline="">
 </video>
 <figcaption>
  It disappears every time you try to inspect it!
 </figcaption>
</figure>

<p>The workaround I went with was one of my favourite little accessibility trade
secrets: <code class="language-plaintext highlighter-rouge">setTimeout</code>, <code class="language-plaintext highlighter-rouge">console.log</code>, and <code class="language-plaintext highlighter-rouge">innerHTML</code>! You give up on the
inspect element tool and go to the console instead. You rig up a <code class="language-plaintext highlighter-rouge">setTimeout</code>
that will wait 2 seconds before printing the HTML of the element you’re
interested in, then you race over to get the UI into the state you’re curious
about in time for the timeout to fire.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>setTimeout(() =&gt;
 console.log(
  document
   .querySelector('.suggestions')
   .innerHTML
), 2000)
</code></pre></div></div>

<figure>
 <video poster="/2026/03/28/yes@1000x544.webp" src="/2026/03/28/yes@1000x544.mp4" title="Browser debugging session" controls="" preload="none" playsinline="">
 </video>
 <figcaption>
  There it is!
 </figcaption>
</figure>

<p>Thanks to that trick, it became clear that the combobox items lacked unique IDs,
which prevented <code class="language-plaintext highlighter-rouge">@github/text-expander-element</code> from setting
<a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-activedescendant"><code class="language-plaintext highlighter-rouge">aria-activedescendant</code></a>
properly on the textarea element. From there, it was an <a href="https://codeberg.org/forgejo/forgejo/pulls/11860">easy pull
request</a> to put together.</p>

<p>By the way, did you spot the console warning in the second video?</p>

<blockquote>
  <p>Empty string passed to getElementById()</p>
</blockquote>

<p>Firefox is fucking <em>brilliant</em> for that kind of stuff. That warning is coming
from <code class="language-plaintext highlighter-rouge">@github/combobox-nav</code>, and it’s caused by the same lack of unique IDs
that break the screen reader accessibility of the dropdown. Clicking on the
line number in the stack trace showed me exactly where the missing IDs were
causing trouble, and helped me quickly build a mental model of what was wrong.
So second tip: debug in Firefox!</p>

<p>It’s a lot of fun working on Foregejo’s accessibility. The UI has a very
late-2000’s / early-2010’s architecture, with lots of jQuery. A big strength
I’ve noticed about this is that it seems to impose a fairly low ceiling on code
complexity, discouraging ambitious metaprogramming. The downside of that
trade-off is well understood thanks to a decade of React hegemony, but I want
it in writing that I was surprised by how approachable and understandable I
found this type of code even after so many years away from it. It reminds me of
the internals of the <a href="/2026/03/11/restoring-an-old-80s-boombox/">80’s boombox I’ve
been slowly working on refurbishing</a>,
and I mean that as an enormous complement.</p>

<p>It would be very cool if there was more competition over all these interesting
little Forgejo accessibility puzzles. If it’s something you’re into, I can
reassure you that Forgejo’s the kind of project where accessibility bug reports
and fixes are well-received. Come join us in
<a href="https://matrix.to/#/#forgejo-accessibility:matrix.org">#forgejo-accessibility</a>
and let’s make the new home of free and open source software accessible to everyone!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="accessibility" />
      
        <category term="blog" />
      
        <category term="forgejo" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[Web accessibility is mostly about HTML, I reckon. Tons of the bugs you work on end up being about adding or removing some attribute from an element. So the inspect element tool is a really important part of the toolkit. And most of the time you don’t even have to think about it: just ⌘⇧C, click on the element you’re curious about, and you’re in business.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/03/28/no@1000x544.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/03/28/no@1000x544.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Testing in JAWS on a Mac</title>
      <link href="https://henry.catalinismith.se/2026/03/25/testing-in-jaws-on-a-mac/" rel="alternate" type="text/html" title="Testing in JAWS on a Mac" />
      <published>2026-03-25T00:00:00+01:00</published>
      <updated>2026-03-25T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/03/25/testing-in-jaws-on-a-mac</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/03/25/testing-in-jaws-on-a-mac/"><![CDATA[<p>A
<a href="https://codeberg.org/forgejo/forgejo/pulls/10615">tricky-looking screen reader bug in Forgejo</a>
inspired me to spin up a JAWS testing environment on my Mac.</p>

<p>JAWS is one of the most widely used screen readers in the world and its
behaviour is quite different to the VoiceOver screen reader that Apple
ships with macOS. I’ve always been a little bit in awe of accessibility pros
who have a JAWS testing setup, and have long wanted to join that club. Here’s
how it went down!</p>

<h2 id="install-vmware">Install VMWare</h2>

<p><a href="https://www.virtualbox.org/wiki/Downloads">https://www.virtualbox.org/wiki/Downloads</a></p>

<figure>
 <img alt="
   The VMWare download page, saying VirtualBox Platform Packages
   Windows hosts
   macOS / Intel hosts
   macos / Apple Silicon hosts
   Linux distributions
   Solaris hosts
   Solaris 11 IPS hosts
  " src="/2026/03/25/vmware@1434x856.webp" />
 <figcaption>
  I clicked 'macOS / Apple Silicon hosts'.
 </figcaption>
</figure>

<h2 id="download-windows-11">Download Windows 11</h2>

<p><a href="https://www.microsoft.com/en-us/software-download/windows11arm64">https://www.microsoft.com/en-us/software-download/windows11arm64</a></p>

<figure>
 <img alt="Website screenshot saying Download Windows 11 for Arm-based PCs" src="/2026/03/25/windows11@1426x846.webp" />
</figure>

<h2 id="buy-a-windows-11-product-key">Buy a Windows 11 product key</h2>

<p><a href="https://www.gamekeys.se/produkt/windows-11-home-retail">https://www.gamekeys.se/produkt/windows-11-home-retail</a></p>

<figure>
 <img alt="Website screenshot saying gamekeys.se, Windows 11 Home Retail, 129kr" src="/2026/03/25/gamekeys@1428x840.webp" />
</figure>

<h2 id="start-windows">Start Windows</h2>

<figure>
 <img alt="
   Installing 42%
   Please keep your computer on.
   Your computer may restart a few times.
  " src="/2026/03/25/installing@1566x1019.webp" />
 <figcaption>
  This part took a while.
 </figcaption>
</figure>

<h2 id="install-jaws">Install JAWS</h2>

<p><a href="https://support.freedomscientific.com/Downloads/JAWS">https://support.freedomscientific.com/Downloads/JAWS</a></p>

<figure>
 <img alt="
   Pertorming JAWS Install
   Press Escape to Cancel
   Status: Acquiring…
  " src="/2026/03/25/jawsinstall@1732x1223.webp" />
</figure>

<h2 id="start-jaws">Start JAWS</h2>

<p>Starting JAWS at this point was as simple as double clicking its icon in the
Windows start menu. You get a 40 minute free trial before needing to pay for
JAWS, which is more than enough for typical exploratory manual testing. Here’s
the screen recording I produced while attempting to reproduce the Forgejo bug.</p>

<figure>
 <video controls="" loop="" muted="" playsinline="" preload="none" poster="/2026/03/25/jaws@1024x726.webp" src="/2026/03/25/jaws@1024x724.mp4"></video>
</figure>

<p>Reproducing the bug in this case would have meant JAWS not picking up the
relationship between the checkboxes and their labels. A blind user has reported
that the use of JavaScript to dynamically insert <code class="language-plaintext highlighter-rouge">for</code> and <code class="language-plaintext highlighter-rouge">id</code> attributes to
retrofit proper relationships onto form controls isn’t working for their screen
reader, and I thought this sounded like a likely JAWS issue, with the worst
case scenario being that all JAWS users might be affected.</p>

<p>The video above enables us to rule out that worst case scenario. I’ve replied
<a href="https://codeberg.org/forgejo/forgejo/pulls/10615#issuecomment-12053496">asking for more detailed information</a>
about the environment necessary to reproduce the problem.</p>

<p>Setting this up cost me 120kr and about 45 minutes of my time. Wasn’t even half
as tricky as I’d always imagined it’d be. I’m excited to see what kind of
trouble I can find now that I’m armed with this new capability!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="accessibility" />
      
        <category term="blog" />
      
        <category term="forgejo" />
      

      
      
        <summary type="html"><![CDATA[A tricky-looking screen reader bug in Forgejo inspired me to spin up a JAWS testing environment on my Mac.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/03/25/jaws@1024x726.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/03/25/jaws@1024x726.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Do you really need those line-clamps and nowraps?</title>
      <link href="https://henry.catalinismith.se/2026/03/20/do-you-really-need-those-line-clamps-and-nowraps/" rel="alternate" type="text/html" title="Do you really need those line-clamps and nowraps?" />
      <published>2026-03-20T00:00:00+01:00</published>
      <updated>2026-03-20T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/03/20/do-you-really-need-those-line-clamps-and-nowraps</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/03/20/do-you-really-need-those-line-clamps-and-nowraps/"><![CDATA[<p>After getting
<a href="https://codeberg.org/forgejo/forgejo/pulls/11607">my first Forgejo PR</a> merged
today I was struck by the realisation that I finally have a linkable public
example of a type of accessibility bugfix that’s been a recurring thing for
years across basically every site and app I’ve ever worked on. So I can finally
get this off my chest. Everyone’s CSS is full of line-clamps and nowraps that
serve little to no practical purpose, and prevent visually impaired users from
accessing content.</p>

<h2 id="whats-line-clamp-and-nowrap">What’s line-clamp and nowrap?</h2>

<p>The
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/line-clamp">line-clamp</a>
CSS property lets you limit how many lines a given piece of text can take up. If
you set <code class="language-plaintext highlighter-rouge">line-clamp: 2</code>, for example, then that element’s content will be
truncated to two lines, and everything after that is chopped off and hidden.</p>

<p>The
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/white-space">white-space</a>
CSS property has a value called <code class="language-plaintext highlighter-rouge">nowrap</code> that lets you force the text in an
element not to wrap onto multiple lines. By setting <code class="language-plaintext highlighter-rouge">white-space: nowrap</code>,
you’re disabling text reflow on your element’s text content in favour of one
long line. Tailwind’s <code class="language-plaintext highlighter-rouge">truncate</code> class demonstrates the most commonly used
nowrap setup.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
</code></pre></div></div>

<p>The above forces your text to fit on one line and adds a “…” ellipsis on the end
if anything needs chopping off to make it fit.</p>

<h2 id="and-this-is-everywhere-is-it">And this is “everywhere”, is it?</h2>

<p>Not only is it absolutely everywhere, it <em>has been</em> everywhere for ages.</p>

<p>Right now, in 2026, shadcn/ui is the most popular open source component library
in the world. Its button component includes <code class="language-plaintext highlighter-rouge">nowrap</code>.</p>

<p>Ten years ago, back in 2016, I was at a well known global tech company who were
hiring a lot, and so I spent a lot of time in coding interviews. Part of the
interview had candidates build a button in HTML and CSS. Including <code class="language-plaintext highlighter-rouge">nowrap</code> in
your solution gave you <em>extra credit</em> in the evaluation criteria for that
interview, because that’s the level of mindshare that this practice has enjoyed.</p>

<h2 id="and-thats-a-problem">And that’s a problem?</h2>

<p>People with visual impairments tend to configure their devices to use larger
text. This adaptation reduces the impact that their impaired vision has on their
lives. That this is possible at all is a very cool thing about computers! If you
think about it, digitalisation is society’s big chance to deprecate lots of this
sort of structural oppression.</p>

<p>The whole point of line-clamp and nowrap is that they limit how much space text
can take up on the screen. And when text is larger, it takes up more space on
the screen. So users of large text almost always lose access to some of the
content in elements where line-clamp or nowrap are applied.</p>

<p>Here’s an example of a very typical card component that any web developer
anywhere might be tasked with implementing. The heading and button have
<code class="language-plaintext highlighter-rouge">nowrap</code>, and the text has <code class="language-plaintext highlighter-rouge">line-clamp: 5</code>. It’s at normal 100% text size here,
where those styles aren’t affecting the content.</p>

<figure>
 <img src="/2026/03/20/100@1026x1200.webp" alt="
    Duck photo.
    Heading reads 'Navy Seal Copypasta'.
    Paragraph reads 'What the fuck did you just fucking say about me, you little bitch? I'll have you know I graduated top of my class in the Navy Seals, and I've been involved in numerous secret raids on Al-Quaeda, and I have over 300 confirmed kills.'
    Button reads 'Check MOT history of a vehicle'
  " />
</figure>

<p>It’s a really common defensive practice to chuck those kinds of styles on card
components like these. You’re working from a Figma design that showcases a few
of them in a row or column, and they look <em>great</em> there, thanks to the neat,
rhythmic uniformity of the layout. You’re taking pride in your work, and you
really want the users of your component to enjoy that same gorgeous design
vision that you saw in Figma. You add these layout constraints in good faith,
with love in your heart.</p>

<p>But here’s what a visually impaired user with text at 200% sees.</p>

<figure>
 <img src="/2026/03/20/200@1026x1208.webp" alt="
    Duck photo.
    Heading reads 'Navy Seal Co…'.
    Paragraph reads 'What the fuck did you just fucking say about me, you little bitch? I'll have you know I graduated top of my class in the Navy…'
    Button reads 'Check MOT history of a v…'
  " />
</figure>

<p>Navy Seal Contest? Navy Seal Cough Medicine? Check MOT history of a van? Of a
Volkswagen? Your visually impaired user may never find out.</p>

<p>This is what
<a href="https://www.w3.org/WAI/WCAG21/Understanding/resize-text.html">WCAG success criterion 1.4.4</a>
is all about.</p>

<blockquote>
  <p>Except for captions and images of text, text can be resized without assistive
technology up to 200 percent without loss of content or functionality.</p>
</blockquote>

<h2 id="what-should-i-do-instead">What should I do instead?</h2>

<p>The best move here is to stay out of it and let the browser do its thing. If you
find yourself tempted to chuck in a line-wrap or a nowrap, challenge the
impulse. Do you need it for an important reason such as for constraining the
height of an element inside a virtualised list? If not, probably just don’t
bother then.</p>

<p>It can be tempting to try fine-tuning your line-clamps to make things just
barely compliant at 200% zoom. I think this is the wrong mindset, and stems from
a misconception that since accessibility can sometimes be challenging, then
practicing it well must require an <em>increase</em> in engineering effort. Handling
text resizing is a wonderful example of how accessibility often wants you to do
<em>less</em> rather than <em>more</em>.</p>

<p>It’s true, your button component won’t look quite as good when its text content
reflows onto multiple lines at 200% zoom. But the entire reason why your button
component exists is for people to read its label and then click it if the text
matches their current goal. So what’s more important?</p>

<p>Your visually impaired users care more about accomplishing their goals than
experiencing a particular layout. And your platform – the web browser – is great
at reflowing text to help them with that. The web browser is a piece of
real-world magic capable of breaking down a barrier in a way that would have
been unimaginable 100 years ago, and as a web developer, all you have to do is
to let it.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="accessibility" />
      
        <category term="forgejo" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[After getting my first Forgejo PR merged today I was struck by the realisation that I finally have a linkable public example of a type of accessibility bugfix that’s been a recurring thing for years across basically every site and app I’ve ever worked on. So I can finally get this off my chest. Everyone’s CSS is full of line-clamps and nowraps that serve little to no practical purpose, and prevent visually impaired users from accessing content.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/03/20/100@1026x1200.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/03/20/100@1026x1200.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Assigned Mail At Birth</title>
      <link href="https://henry.catalinismith.se/2026/03/14/assigned-mail-at-birth/" rel="alternate" type="text/html" title="Assigned Mail At Birth" />
      <published>2026-03-14T00:00:00+01:00</published>
      <updated>2026-03-14T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/03/14/assigned-mail-at-birth</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/03/14/assigned-mail-at-birth/"><![CDATA[<p>A mailwoman in the US was receiving orthopedic treatment for issues relating to
the physical demands of her job. There’s a lot of lifting, carrying, and walking
involved in delivering mail, which can lead to pain and inflammation. One day,
when checking her medical documentation, she noticed the orthopedist had
suddenly and incorrectly reregistered her as a transgender person.</p>

<figure>
 <img src="/2026/03/14/amab@1024x597.webp" alt="
   History of Present Illness
   Patient is a 25 year old female Postal Carrier who comes in 2 weeks. She states the pain feels like whe..
   History of Present Illness Patient is a 26 year old biological male identifying
   as a female who c pain to the left neck, trap, and clavicle. Her pain gets worse
  " />
 <figcaption>
  <a href="https://old.reddit.com/r/mildlyinfuriating/comments/1rsnthn/sometime_during_the_last_2_years_ive_been_going/">
   Sometime during the last 2 years i’ve been going to this orthopedic practice they started to declare me as a MTF transgender for no reason.
  </a>
 </figcaption>
</figure>

<p>Respondents on Reddit were quick to point out that AI usage was the likely root
cause. At some point an AI transcription service has mistranscribed “mail” as
“male”. And since the point of using an AI transcription service in the first
place is that slop is quicker and cheaper than medical records created by a
qualified medical professional, the mistranscription became a documented medical
“fact”.</p>

<p>Further replies suggest that it’s far from an isolated incident.</p>

<blockquote>
  <p>My doc has me listed as having 2 children. I have 2 cats. Pretty sure it’s the
ai thing they use to take notes.</p>

  <p><a href="https://old.reddit.com/r/mildlyinfuriating/comments/1rsnthn/sometime_during_the_last_2_years_ive_been_going/oa8auqj/">TollyMune</a></p>
</blockquote>

<blockquote>
  <p>Yeah, I have nurse friends who have been forced to use shitty AI tools for
notes with disastrous results, and this could be one of those mistakes.</p>

  <p><a href="https://old.reddit.com/r/mildlyinfuriating/comments/1rsnthn/sometime_during_the_last_2_years_ive_been_going/oa8etvt/">jensized</a></p>
</blockquote>

<blockquote>
  <p>you need to write/document the issue, why it’s incorrect, and request the
doctor (or charting medical provider) to make the correction. I had to do it
last year for something similar to OP where it was a huge, and extremely
concerning, error and I actually switched providers over it due to the
implications. <br /><br /> Given this particular mistake (mine was more
abuse/neglect oriented for my child and was an AI hallucination from their AI
charting program) and the current political climate I would absolutely blast
this provider and find the root issue and potentially report it all the way
up.</p>

  <p><a href="https://old.reddit.com/r/mildlyinfuriating/comments/1rsnthn/sometime_during_the_last_2_years_ive_been_going/oa8l0xx/">TurtleScientific</a></p>
</blockquote>

<blockquote>
  <p>If your doctor uses AI that could be it… I recently went to a new doctor who
uses AI to transcribe and I told him I was engaged. I look at the
transcription and it says me: “I am gay” doctor:“you are gay?” Me: “Yeah” 😂😂</p>

  <p><a href="https://old.reddit.com/r/mildlyinfuriating/comments/1rsnthn/sometime_during_the_last_2_years_ive_been_going/oa8fp3l/">Euphoric_Cicada_2486</a></p>
</blockquote>

<blockquote>
  <p>They were 100% using AI for chart notes, it’s very common nowadays. However
they’re SUPPOSED to review for accuracy, but a lot of them don’t. It’s the
bane of my existence as a medical coder.</p>

  <p><a href="https://old.reddit.com/r/mildlyinfuriating/comments/1rsnthn/sometime_during_the_last_2_years_ive_been_going/oaahk7m/">DumpsterPuff</a></p>
</blockquote>

<blockquote>
  <p>Our AI note translator translated that a patient has a hot mama instead of a
heart murmur. Patients mama was indeed hot but thankfully never asked for a
history before the error was picked up.</p>

  <p><a href="https://old.reddit.com/r/mildlyinfuriating/comments/1rsnthn/sometime_during_the_last_2_years_ive_been_going/oa952hd/">Free-Rice-2808</a></p>
</blockquote>

<p>Here in Sweden, this brings exactly one company to mind. Oracle.</p>

<p>Oracle are the masterminds of the disastrous
“<a href="https://sv.wikipedia.org/wiki/Millennium_(journalsystem)">Millenium</a>”
patient journal system. Millenium is best known for being so incredibly bad that
healthcare workers literally
<a href="https://www.svt.se/nyheter/lokalt/vast/protest-mot-journalsystemet-millennium-utanfor-sodra-alvsborgs-sjukhus">protested outside hospitals</a>
to try to stop it from being rolled out due to patient safety fears.</p>

<p>The same two regions of Sweden who were at the epicentre of the Millenium
scandal - Västra Götaland and Skåne - were reported in 2024 to have
<a href="https://it-halsa.se/ai-driven-digital-assistent-forbattrar/">integrated a service called Oracle Clinical Digital Assistant</a>,
which has since been renamed Oracle Health Clinical AI Agent.</p>

<iframe id="yt-player-KA717mJyNHY" style="width: 100%; aspect-ratio: 4/3;" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" src="https://www.youtube-nocookie.com/embed/KA717mJyNHY?autoplay=1&amp;listType=playlist&amp;hl=en-US&amp;enablejsapi=1&amp;origin=https%3A%2F%2Fwww.oracle.com&amp;playerId=KA717mJyNHY"></iframe>

<blockquote>
  <p>Immediately after capturing and analyzing a conversation between you and your
patient, Oracle Health Clinical AI Agent automatically generates a structured
note for you.</p>
</blockquote>

<p>Good luck out there everyone!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[A mailwoman in the US was receiving orthopedic treatment for issues relating to the physical demands of her job. There’s a lot of lifting, carrying, and walking involved in delivering mail, which can lead to pain and inflammation. One day, when checking her medical documentation, she noticed the orthopedist had suddenly and incorrectly reregistered her as a transgender person.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/03/14/amab@1024x597.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/03/14/amab@1024x597.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Oblong of Dreams</title>
      <link href="https://henry.catalinismith.se/2026/03/13/oblong-of-dreams/" rel="alternate" type="text/html" title="Oblong of Dreams" />
      <published>2026-03-13T00:00:00+01:00</published>
      <updated>2026-03-13T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/03/13/oblong-of-dreams</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/03/13/oblong-of-dreams/"><![CDATA[<p>I first discovered Half Man Half Biscuit while living in Bristol and working on
the music search UI at MixRadio. You type a lot of test search queries when
you’re building a search form. It inevitably becomes swear words, which is how I
stumbled on <a href="https://www.youtube.com/watch?v=hg043e2QL4A">National Shite Day</a>.</p>

<blockquote>
  <p>Down in the High Street somebody careered out of Boots without due care or
attention<br /> I suggest that they learn some pedestrian etiquette<br /> i.e
sidle out of the store gingerly<br /> Embrace the margin<br /></p>
</blockquote>

<p>If I was the kind of person who had a personal anthem, National Shite Day might
be it. Homesickness for the Wirral had become a big part of my life by then, and
it meant the world to me to have discovered this band from back home through a
search for a word from back home.</p>

<figure>
 <audio controls="" src="/2026/03/13/En-uk-shite.ogg"></audio>
 <figcaption>
  Incidentally, the Wikipedia audio sample for the word "shite" provides a great opportunity to experience how weird it sounds to hear it in a southern accent.
 </figcaption>
</figure>

<p>On top of that, the lyrics narrate a walk through Birkenhead town centre, which
was a magical place to me in the late 90s and early 2000s. Like, it was where
you went to buy CDs, that kind of thing. Listening to National Shite Day became
a way to reach back across time and space to <em>that</em>.</p>

<figure>
 <img src="/2026/03/13/birkenhead@1024x781.webp" alt="Map of Birkenhead. Birkenhead market and the Pyramids shopping centre are visible." />
 <figcaption>
  Literally the most exciting place I knew as a kid
 </figcaption>
</figure>

<p>It also felt kind of cool that you practically have to <em>be</em> from the Wirral to
stand a chance of understanding some of their songs. The layout of the town
centre in National Shite Day is one example. Another one is in
<a href="https://www.youtube.com/watch?v=cGD8ir1KU6U">Twydale’s Lament</a>.</p>

<blockquote>
  <p>Gouranga Gouranga<br /> Yes I’ll be happy<br /> When you’ve been arrested for
defacing the bridge</p>
</blockquote>

<p>There used to be a good few bridges in the north west of England with Gouranga
spraypainted onto them, but there’s a very specific one over the M56 that had it
for absolutely <em>years</em> when I was a kid. I think the line’s about that one in
particular, because everyone in the Wirral has to drive down the M56 anytime
they go almost anywhere except Liverpool.</p>

<figure>
 <img src="/2026/03/13/gouranga@1024x683.webp" alt="Motorway scene with a bridge in the middle distance. Traffic is heavy." />
 <figcaption>
  This one, I think, except on the other side. Photo thanks to <a href="https://www.geograph.org.uk/photo/4597564">Bill Boaden</a>.
 </figcaption>
</figure>

<p>These little things became a roundabout way to feel connected to home despite
being physically far away.</p>

<p>I started to lose touch with this music a little bit during the “peak
digitalisation” era of my life from around 2018 to 2022. Streaming services
don’t really exist to let you listen to your collection of albums in peace.
Playlists and recommendations give them music industry leverage and lock you
into their services more, so they want you on those, and a small army of
talented people works to adjust your listening in that direction through
thousands of subtle nudges.</p>

<p>The return of intentionality to music listening since waking up from that has
been like rebirth. The scarcity of needing a CD or a record changes the whole
experience. You stop using music as 24/7 white noise and start actually
listening to it again.</p>

<p>And speaking of needing CDs, once during a trip home I had a spare couple of
hours to myself, and decided to see if there was a good record store to visit in
Birkenhead. I discovered Skeleton Records, which my dad used to go to as a kid.</p>

<figure>
 <img src="/2026/03/13/skeleton@1024x1365.webp" alt="Red brick building with shop signage on the front and a red entrance sign pointing down at the door." />
</figure>

<p>My guess was that a place like that in Birkenhead would probably have some Half
Man Half Biscuit, and I was more right than expected. Not only did I pick up 11
albums, but the lad behind the counter seemed to know the band personally, and
had even played trumpet and keyboard on a couple of tracks in The Voltarol
Years. I was smiling like a mad person the whole way home.</p>

<figure>
 <img src="/2026/03/13/hmhb@768x1024.webp" alt="A stack of Half Man Half Biscuit albums in CD format." />
</figure>

<p>Bringing this haul of music back to Sweden on the plane felt like Midnight
Express. I’d forgotten how special CDs that you’ve bought on holiday feel, too.
I’ve been slowly working my way through it ever since and finally got to The
Voltarol Years this week. And then it happened.
<a href="https://www.youtube.com/watch?v=Kt7A3fMpP1I">Oblong of Dreams</a>. Fucking hell.</p>

<figure>
 <img src="/2026/03/13/wirral@1024x773.webp" alt="Map of the Wirral, which is a peninsula with an oblong shape." />
 <figcaption>
  The oblong in question
 </figcaption>
</figure>

<p>The whole song is one big walk around the Wirral. It hit me like a fucking
train. I first listened to it late in the evening, and had trouble getting to
sleep after. That CD on the shelf now rivals the
<a href="/2025/11/17/give-me-lava-chicken-or-give-me-death/">concentrated psychic power of the Aristocats soundtrack record we bought last year</a>.</p>

<p>It wasn’t just the nostalgia trip that did it, either. And it’s <em>some</em> nostalgia
trip, even crossing the river next to the farm I grew up on. But there was
something more, like something supernaturally, recursively cool about the fact
that I’d finally gotten this chance to return to the place from National Shite
Day, relive that childhood memory of buying CDs there, and it had in turn
triggered this encore of the original experience of discovering National Shite
Day itself and brought back even more memories.</p>

<p>There’s a lot of talk about stuff like
“<a href="https://en.wikipedia.org/wiki/Friction-maxxing">friction-maxxing</a>” at the
moment. I think there’s a growing awareness that overdigitalisation and an
obsession with efficiency have begun to optimise away some of the meaning and
human connection from life. Rather than fixate on the critique itself, I think
it’s interesting with positive examples of what the alternative can look like,
and I reckon my copy of The Voltarol Years is a decent one of those.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="music" />
      

      
      
        <summary type="html"><![CDATA[I first discovered Half Man Half Biscuit while living in Bristol and working on the music search UI at MixRadio. You type a lot of test search queries when you’re building a search form. It inevitably becomes swear words, which is how I stumbled on National Shite Day.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/03/13/birkenhead@1024x781.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/03/13/birkenhead@1024x781.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Golang LSP in Helix on macOS</title>
      <link href="https://henry.catalinismith.se/2026/03/12/golang-lsp-in-helix-on-macos/" rel="alternate" type="text/html" title="Golang LSP in Helix on macOS" />
      <published>2026-03-12T00:00:00+01:00</published>
      <updated>2026-03-12T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/03/12/golang-lsp-in-helix-on-macos</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/03/12/golang-lsp-in-helix-on-macos/"><![CDATA[<p>Since
<a href="2026/03/10/running-a-forgejo-dev-environment/">getting my Forgejo dev environment running</a>
I’ve begun poking around the
<a href="https://codeberg.org/forgejo/forgejo/issues">issue backlog</a> and learning my way
around the code, looking for the stepping stones to becoming a contributor. One
important early milestone was to get the Golang LSP running in Helix.</p>

<p>You can always check if Helix’s LSP integration for a given language is ready by
using <code class="language-plaintext highlighter-rouge">hx --health</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ hx --health go
Configured language servers:
  ✘ gopls: 'gopls' not found in $PATH
  ✘ golangci-lint-lsp: 'golangci-lint-langserver' not found in $PATH
Configured debug adapter:
  ✘ 'dlv' not found in $PATH
Configured formatter: None
Tree-sitter parser: ✓
Highlight queries: ✓
Textobject queries: ✓
Indent queries: ✓
</code></pre></div></div>

<p>Those ✘ symbols are bad news! Installing those missing tools looked like this.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go install golang.org/x/tools/gopls@latest
go install github.com/nametake/golangci-lint-langserver@latest
go install github.com/go-delve/delve/cmd/dlv@latest
brew install golangci-lint
</code></pre></div></div>

<p>That last <code class="language-plaintext highlighter-rouge">brew install</code> there is good to know about. You’ll get ✓✓✓ from
<code class="language-plaintext highlighter-rouge">hx --health</code> without it, but linting won’t actually work, and instead there’s
an error about <code class="language-plaintext highlighter-rouge">exec: "golangci-lint": executable not found in $PATH</code>.</p>

<p>Then there was one last little trick necessary. Those things that <code class="language-plaintext highlighter-rouge">go install</code>
downloaded live in <code class="language-plaintext highlighter-rouge">~/go/bin</code>, which isn’t in <code class="language-plaintext highlighter-rouge">$PATH</code>. I threw a Fish plugin at
the problem, like this.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>fisher install halostatue/fish-go@v2
</code></pre></div></div>

<p>After that, I had ✓ across the board.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ hx --health go
Configured language servers:
  ✓ gopls: /Users/henrycatalinismith/go/bin/gopls
  ✓ golangci-lint-lsp: /Users/henrycatalinismith/go/bin/golangci-lint-langserver
Configured debug adapter:
  ✓ /Users/henrycatalinismith/go/bin/dlv
Configured formatter: None
Tree-sitter parser: ✓
Highlight queries: ✓
Textobject queries: ✓
Indent queries: ✓
</code></pre></div></div>

<p>Here’s Helix in action with the tooling integrated, successfully detecting a
linter error resulting from an unused variable.</p>

<figure>
 <img src="/2026/03/12/error@1024x518.webp" alt="Helix screenshot showing an unused variable x := 0 being annotated as 'declared and not used: x'" />
</figure>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      
        <category term="forgejo" />
      

      
      
        <summary type="html"><![CDATA[Since getting my Forgejo dev environment running I’ve begun poking around the issue backlog and learning my way around the code, looking for the stepping stones to becoming a contributor. One important early milestone was to get the Golang LSP running in Helix.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/03/12/error@1024x518.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/03/12/error@1024x518.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Restoring an old 80’s boombox</title>
      <link href="https://henry.catalinismith.se/2026/03/11/restoring-an-old-80s-boombox/" rel="alternate" type="text/html" title="Restoring an old 80’s boombox" />
      <published>2026-03-11T00:00:00+01:00</published>
      <updated>2026-03-11T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/03/11/restoring-an-old-80s-boombox</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/03/11/restoring-an-old-80s-boombox/"><![CDATA[<p>We picked up this gorgeous Philips D8569 boombox from 1982 at a second-hand
place recently.</p>

<figure>
 <img src="/2026/03/11/IMG_0435@1024x768.webp" alt="Black boombox with silver trim, two tape decks, two speakers and a radio antenna" />
</figure>

<p>When we got it home and put a tape in it, the sound was really muddy. And worse,
every time you moved one of the volume sliders even a tiny bit, it produced an
ear-splitting crackle at maximum volume from both speakers. We were almost going
to try to return it when I thought “I bet it’d be fun to try to fix it instead”.</p>

<p>So I ordered a can of DeoxtIT D5 spray to deoxidise the volume sliders, some
tape head cleaner and demagnetiser, and some plastic polish to try to knock a
few years of aging off its appearance.</p>

<figure>
 <img src="/2026/03/11/IMG_0436@1024x768.webp" alt="'Vinyl Styl' branded cassette head cleaner &amp; demagnetiser, DeoxitD5 spray can, and three bottles of plastic polish" />
 <figcaption>
  This stuff cost more than the boombox itself.
 </figcaption>
</figure>

<p>Deoxidising the sliders meant partially disassembling the thing. It was quite
exciting to see inside an early 80’s music player.</p>

<figure>
 <img src="/2026/03/11/IMG_0437@1024x768.webp" alt="Disassembled boombox with the front removed. Circuit boards and tape decks and other internals are visible." />
 <figcaption>
  You can see a jammed pause button on the left hand side of this photo.
 </figcaption>
</figure>

<p>The adjustable potentiometers were on this brown circuit board underneath.</p>

<figure>
 <img src="/2026/03/11/IMG_0438@1024x768.webp" alt="Brown circuit board with some sliding adjustable potentiometers. In the background the DeoxitD5 spray is visible." />
</figure>

<p>I sprayed them a little tiny bit, moved the sliders back and forth a whole
bunch, then dried it off as best as I could. After reassembly I ran the cleaner
tape and then gave the whole thing a quick polish.</p>

<p>The result is a working boombox, give or take a few remaining issues. I’m not
100% convinced the tape decks are finished yet. One has a pause button that’s
stuck, so that one is out of action until I’m feeling brave enough to
disassemble that whole button mechanism. The other one sounds a ton better but
maybe not perfect yet. The volume slider crackle is gone though.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="hardware" />
      
        <category term="music" />
      

      
      
        <summary type="html"><![CDATA[We picked up this gorgeous Philips D8569 boombox from 1982 at a second-hand place recently.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/03/11/IMG_0435@1024x768.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/03/11/IMG_0435@1024x768.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Running a Forgejo dev environment</title>
      <link href="https://henry.catalinismith.se/2026/03/10/running-a-forgejo-dev-environment/" rel="alternate" type="text/html" title="Running a Forgejo dev environment" />
      <published>2026-03-10T00:00:00+01:00</published>
      <updated>2026-03-10T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/03/10/running-a-forgejo-dev-environment</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/03/10/running-a-forgejo-dev-environment/"><![CDATA[<p>Been wanting to get started making code contributions to Codeberg for the
longest time. Finally got around to setting up a local development environment.
Here’s my notes!</p>

<p>First up, we need <code class="language-plaintext highlighter-rouge">go</code>, <code class="language-plaintext highlighter-rouge">node</code> and <code class="language-plaintext highlighter-rouge">postgresql</code> installed.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ brew install go node postgresql@18
</code></pre></div></div>

<p>Then we need to clone the repo.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone ssh://git@codeberg.org/forgejo/forgejo.git
$ cd forgejo
</code></pre></div></div>

<p>The frontend and backend dependencies need to be downloaded.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install
$ go mod download
</code></pre></div></div>

<p>Then we build everything and cross our fingers for no erros.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ make frontend
$ make backend
</code></pre></div></div>

<p>The Postgres database needs some special care. This was the most time-consuming
part of the process for me. It took a bit of trial and error to understand that
I needed to do all this stuff.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ brew services start postgresql@18
==&gt; Successfully started `postgresql@18` (label: homebrew.mxcl.postgresql@18)
$ createuser -s postgres
$ psql -h localhost -U postgres
$ psql -h localhost -U postgres
psql (18.3 (Homebrew))
Type "help" for help.

postgres=# CREATE ROLE forgejo WITH LOGIN PASSWORD ';';
CREATE ROLE
postgres=# CREATE DATABASE forgejo OWNER forgejo;
CREATE DATABASE
postgres=# GRANT ALL PRIVILEGES ON DATABASE forgejo TO forgejo;
GRANT
</code></pre></div></div>

<p>Now all that’s left is to fire up the server.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ go run . -w $(pwd)
</code></pre></div></div>

<p>With that running, http://localhost:3000 should start to load. After completing
a setup form, it should look like this.</p>

<figure>
 <img src="/2026/03/10/localhost3000@2158x1162.webp" alt="Blank new Forgejo instance showing No activity, There is nothing in your feed yet. Your actions and activity from repositories that you watch will show up here." />
</figure>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="codeberg" />
      
        <category term="devtools" />
      
        <category term="forgejo" />
      

      
      
        <summary type="html"><![CDATA[Been wanting to get started making code contributions to Codeberg for the longest time. Finally got around to setting up a local development environment. Here’s my notes!]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/03/10/localhost3000@2158x1162.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/03/10/localhost3000@2158x1162.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">The software job market is going to split in two</title>
      <link href="https://henry.catalinismith.se/2026/03/02/the-software-job-market-is-going-to-split-in-two/" rel="alternate" type="text/html" title="The software job market is going to split in two" />
      <published>2026-03-02T00:00:00+01:00</published>
      <updated>2026-03-02T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/03/02/the-software-job-market-is-going-to-split-in-two</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/03/02/the-software-job-market-is-going-to-split-in-two/"><![CDATA[<p>Found myself in Backagården once again this weekend,
<a href="https://zetkin.org/codecamp/">building software for the international left</a>.
This has been a bit of a happy place for me for some time now. This weekend
bought a surprise happiness bonus, when I realised how much fun I was having
coding without access to my employer-provided Opus 4.5 subscription.</p>

<p>A similar experience about a year earlier saw me switching off Copilot’s code
completion functionality after
<a href="/2025/03/14/survival-mode/">realising how boring it had made everything feel</a>.
This time felt different somehow. While considering the reason for that
difference, it hit me that the real effect of these new tools on the job market
might be becoming clear now.</p>

<p>The difference between the old autocomplete and this agent workflow with Opus
4.5 is that this new thing actually kind of works. Most of the downsides are the
same – worse, even – especially in the increased estrangement from the work. But
the fact that it’s become a viable way to achieve results changes certain
things.</p>

<p>One thing it changes is for all these 80s guys competing to see whose company
can be the most maniacally obsessed with the new golden hammer. I think they’re
going to get to see some of their visions fulfilled. And this means that the
software engineers they employ are going to lose their ability to code
independently.</p>

<figure>
 <img src="/2026/03/02/80sguy@259x194.webp" style="width: 100%" alt="80s guy from Futurama" />
 <figcaption>
  There are two kinds of people: sheep and sharks. Anyone who is a sheep is fired.
  Sharks are winners, and they don't look back 'cause they don't have necks. Necks are for sheep.
  I am proud to be the shepherd of this herd of sharks.
 </figcaption>
</figure>

<p>That last part there is based on what I’ve seen happen in my own abilities
during periods when I’ve leaned more on these frontier models. Switching off
Copilot autocomplete after more than a year of use took me weeks to recover
from. Opus 4.5 had only been in my life a few months when I had my day without
it in Backagården, but I already felt the same nicotine-like craving for it and
the same sensation of rustiness in my thinking while working through problems
independently of the magic answer machine.</p>

<p>I’ve already experienced the humiliation of bombing a coding interview due to
being surprised by a “no Copilot” rule during my first period of dependency. I’m
confident in saying that these more extreme people who’ve completely abandoned
coding in favour of prompting models to write code for them aren’t going to be
able to pass fizzbuzz independently after a year or two. So I think those
people, and the people who want to hire them, are necessarily going to become a
distinct job market with its own approach to things like recruitment.</p>

<p>In a sense, then, I’m accepting that some reduction in the amount of available
coding jobs is likely to be a reality of this new phase of the job market. I’m
not convinced it’s a major percentage, though. Even accounting for the
improvements in Opus 4.5, I still think it feels more like the invention of the
microwave than the invention of the car. Every commercial kitchen <em>has</em> a
microwave, but Wetherspoons is the only place I can think of that’s well-known
for microwaving <em>everything</em>.</p>

<p>It was a productive day in Backagården, too. First we ripped out isomorphic
rendering in favour of a more isolated REST + SPA architecture. That took three
pull requests (<a href="https://github.com/zetkin/lyra/pull/238">1</a>,
<a href="https://github.com/zetkin/lyra/pull/242">2</a>,
<a href="https://github.com/zetkin/lyra/pull/243">3</a>). Then came a
<a href="https://github.com/zetkin/lyra/pull/244">search feature to find translation strings</a>.
I put a lot of love into handling the various state transitions right and
highlighting matching text, and felt proud of the result in a way that I hadn’t
been feeling after prompting my way through problems. I know which end of the
job market I want to be in.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      
        <category term="zetkin" />
      

      
      
        <summary type="html"><![CDATA[Found myself in Backagården once again this weekend, building software for the international left. This has been a bit of a happy place for me for some time now. This weekend bought a surprise happiness bonus, when I realised how much fun I was having coding without access to my employer-provided Opus 4.5 subscription.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/03/02/80sguy@259x194.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/03/02/80sguy@259x194.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Our NES needed a new 72 pin connector</title>
      <link href="https://henry.catalinismith.se/2026/02/26/our-nes-needed-a-new-72-pin-connector/" rel="alternate" type="text/html" title="Our NES needed a new 72 pin connector" />
      <published>2026-02-26T00:00:00+01:00</published>
      <updated>2026-02-26T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/02/26/our-nes-needed-a-new-72-pin-connector</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/02/26/our-nes-needed-a-new-72-pin-connector/"><![CDATA[<p>Although
<a href="/2026/02/13/repairing-an-old-nes/">thoroughly cleaning our new NES the other day</a>
was enough to get it booting Super Mario Bros, it wasn’t reliable enough. On a
bad day it could take five minutes of fiddling with the position of the
cartridge and resetting the console to get the game running.</p>

<p>The next thing to try was to completely replace the 72 pin connector. These cost
about 150kr, so it’s not a huge investment.</p>

<p>After disassembling the NES again, I had high hopes that the old one would look
visually older and more worn out than the new one. Something to boost my
confidence in the repair before beginning the reassembly. Didn’t really happen.</p>

<figure>
 <img src="/2026/02/26/replaced@1024x1024.webp" alt="2 72 pin connectors for the original 8-bit Nintendo side-by-side on a blue mat" />
 <figcaption>
  From most angles it was impossible to tell the old one from the new one visually.
 </figcaption>
</figure>

<p>You could <em>really</em> tell the difference when inserting the Super Mario Bros
cartridge afterwards though. With the old connector you could practically throw
the cartridge in there. With the new one it became quite unforgiving and
precise.</p>

<p>That difference in feel when inserting the cartridge meant it wasn’t a surprise
at all when Super Mario Bros booted on the very first try after finishing the
reassembly. It seems to be completely refurbished now!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="hardware" />
      

      
      
        <summary type="html"><![CDATA[Although thoroughly cleaning our new NES the other day was enough to get it booting Super Mario Bros, it wasn’t reliable enough. On a bad day it could take five minutes of fiddling with the position of the cartridge and resetting the console to get the game running.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/02/26/replaced@1024x1024.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/02/26/replaced@1024x1024.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">I got a commit merged into Helix!</title>
      <link href="https://henry.catalinismith.se/2026/02/21/i-got-a-commit-merged-into-helix/" rel="alternate" type="text/html" title="I got a commit merged into Helix!" />
      <published>2026-02-21T00:00:00+01:00</published>
      <updated>2026-02-21T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/02/21/i-got-a-commit-merged-into-helix</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/02/21/i-got-a-commit-merged-into-helix/"><![CDATA[<p>After
<a href="/2025/12/07/lazyrename-for-helix-and-zellij/">building lazyrename a couple of months ago</a>
I got curious about small ways of improving Helix’s built-in file moving
functionality. The conclusion I came to was that a
<a href="https://doc.rust-lang.org/std/fs/fn.create_dir_all.html"><code class="language-plaintext highlighter-rouge">fs::create_dir_all()</code></a>
call would make a big difference all on its own, and so I set out to get that
added. The
<a href="https://github.com/helix-editor/helix/pull/15001/">pull request finally got merged</a>
this week, which I’m really excited about.</p>

<p>My first attempt went off in the wrong direction by directly changing how
Helix’s <code class="language-plaintext highlighter-rouge">:move</code> command works. I’m very grateful for the patient feedback that
guided me towards adding a <code class="language-plaintext highlighter-rouge">:move!</code> command instead. Helix uses that exclamation
mark suffix for things with side effects, and there was already a <code class="language-plaintext highlighter-rouge">:write!</code> for
creating necessary subdirectories when writing to a file.</p>

<p>Learning about Helix’s internals has been really fun. It reminds me of some of
the more well-engineered stuff I’ve worked on, with a clear architectural vision
and lots of nice tidy structure.</p>

<p>I think the slow pace of development is probably a big factor in that. My pull
request sat unmerged for <em>months</em> once it was ready. Eventually someone created
an issue about implementing it, and the merge happened when someone noticed the
work was already done. I’ve said before
<a href="/2025/12/06/zellij-and-helix-together-might-actually-be-just-plain-better-than-neovim/">I think Helix is as good as a finished piece of software</a>
already and I stand by it.</p>

<p>This new command makes
<a href="https://codeberg.org/henrycatalinismith/lazyrename">lazyrename</a> partially
obsolete. By
<a href="https://codeberg.org/henrycatalinismith/dotfiles/commit/34879a8e4d627d1de3feaeb2c027787cac4d5be5">adding a keymap</a>
that writes <code class="language-plaintext highlighter-rouge">:mv! %{buffer_name}</code> to Helix without pressing enter, you can
approximate most of that UX in a single line of config.</p>

<figure>
 <video poster="/2026/02/21/bananas@1024x554.webp" src="/2026/02/21/bananas@1024x552.mp4" title="Demo of the :mv! command" controls="" preload="none" playsinline="">
 </video>
</figure>

<p>It’s enough to let you edit the full relative path of the current buffer without
first having to type it in from scratch. For a Next.js project with lots of
<code class="language-plaintext highlighter-rouge">src/app/(something)/foo/baz/bar/page.tsx</code> where you’re generally always moving
<code class="language-plaintext highlighter-rouge">page.tsx</code> to a new directory, that’s probably enough. It lacks support for
things like Alt+Left to jump back to the previous word boundary, but the
increased simplicity might make that a good trade-off.</p>

<p>Very fun to have gotten a commit into the project anyway. In all those years of
Vim usage it never felt like contributing was anywhere near even being on the
horizon. Rust is so goddamn cool.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[After building lazyrename a couple of months ago I got curious about small ways of improving Helix’s built-in file moving functionality. The conclusion I came to was that a fs::create_dir_all() call would make a big difference all on its own, and so I set out to get that added. The pull request finally got merged this week, which I’m really excited about.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/02/21/bananas@1024x554.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/02/21/bananas@1024x554.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Repairing an old NES</title>
      <link href="https://henry.catalinismith.se/2026/02/13/repairing-an-old-nes/" rel="alternate" type="text/html" title="Repairing an old NES" />
      <published>2026-02-13T00:00:00+01:00</published>
      <updated>2026-02-13T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/02/13/repairing-an-old-nes</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/02/13/repairing-an-old-nes/"><![CDATA[<p>Spotting a dirt cheap CRT television in a second hand shop in Trelleborg last
weekend set in motion a chain of events that led to a Nintendo and a copy of
Super Mario Bros arriving in the mail. Only thing is, none of it bloody worked.
I got the old flashing red light when turning it on, and once blowing in the
cartridge didn’t sort it I was out of ideas. Fortunately I found a
<a href="https://www.youtube.com/watch?v=16Ni0hFLkUo">very helpful YouTube video that walked me through fixing it</a>.</p>

<figure>
 <img src="/2026/02/13/before@1024x768.webp" alt="NES on a wooden table next to a repair kit, bottle of isopropyl alcohol, and some electronics mats" />
</figure>

<p>Opening the thing up required a philips screwdriver. The were four screws to
remove and then I was in.</p>

<figure>
 <img src="/2026/02/13/inside@1024x768.webp" alt="Top-down view of a NES with the cover removed. A large green circuit board with some wires and other components around it." />
</figure>

<p>The goal of the disassembly is to remove the 72 pin connector for inspection and
cleaning. To get to it, I had to remove another bunch of philips screws. It was
handy to have a magnetic mat to keep them organised on for this part.</p>

<figure>
 <img src="/2026/02/13/parts@1024x768.webp" alt="A 72 pin connector from a NES and a Super Mario Bros cartridge together on a blue electronics work mat" />
</figure>

<p>Once I had the 72 pin connector out, I cleaned it really thoroughly with some
isopropyl alcohol. The connectors on the cartridge also got a thorough clean.</p>

<p>After that came the reassembly, which went fine. It’s a bit of a leap of faith
at that point as you don’t know if you’ve resolved the issue yet.</p>

<figure>
 <img src="/2026/02/13/after@1024x768.webp" alt="A CRT television next to a powered-on NES. Super Mario Bros is playing on the TV." />
</figure>

<p>Fuck knows what the next step would have been if this hadn’t sorted it. Buying a
new 72 pin connector maybe? Thing is with buying the console and the cartridge
at the same time, you can’t be sure which one the problem’s coming from. In that
sense I was lucky this worked.</p>

<p>Very fun to work with my hands and repair something. Excited to have an old
school Nintendo now as well. Definitely think it’s going to help the kids have a
healthier relationship to their screen time compared to the addictiveness of the
endless choice of the dozens of games installed on the Switch as well.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="hardware" />
      

      
      
        <summary type="html"><![CDATA[Spotting a dirt cheap CRT television in a second hand shop in Trelleborg last weekend set in motion a chain of events that led to a Nintendo and a copy of Super Mario Bros arriving in the mail. Only thing is, none of it bloody worked. I got the old flashing red light when turning it on, and once blowing in the cartridge didn’t sort it I was out of ideas. Fortunately I found a very helpful YouTube video that walked me through fixing it.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/02/13/before@1024x768.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/02/13/before@1024x768.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">The new Swedish citizenship requirements</title>
      <link href="https://henry.catalinismith.se/2026/02/10/the-new-swedish-citizenship-requirements/" rel="alternate" type="text/html" title="The new Swedish citizenship requirements" />
      <published>2026-02-10T00:00:00+01:00</published>
      <updated>2026-02-10T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/02/10/the-new-swedish-citizenship-requirements</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/02/10/the-new-swedish-citizenship-requirements/"><![CDATA[<p>About six years ago I became a Swedish citizen. I’d been in the country around
four years at the time. Being married to a Swedish citizen allowed it to happen
early enough that Brexit never actually cost me my EU citizenship. Pretty nice!
But if the new rules being introduced this year had applied back then I think
I’d still be waiting today.</p>

<p>For starters, they’re raising the minimum qualifying period from five years to
eight. That threshold would have taken me until mid 2024 to meet. Then once you
pass the threshold and submit your application, there’s the processing time to
consider. That’s on the order of several years. I could very easily have found
myself facing the
<a href="/2026/01/22/ten-years-of-sweden/">tenth anniversary of my arrival in Sweden</a>
without a passport to show for it.</p>

<p>It seems likely that the shortened qualifying period when married to a Swedish
citzen may live on in the updated version of the law. There’s currently no
clarity about whether or how much it’ll change though. So lots of people who’ve
done everything right since day one, and filed valid citizenship applications in
the past few years, now have absolutely no idea if they’re going to be sent back
to square one. It’s quite outrageous.</p>

<p>That lack of clarity is a theme with this new law. They’re adding Swedish
languge requirements to the process too, which means there’ll be a test. That
Swedish language <em>test</em> won’t be ready until October 2027, but
<a href="https://www.thelocal.se/20260210/swedens-language-requirement-will-apply-from-june-despite-lack-of-test">the language <em>requirement</em> will apply as of June 2026</a>.
What are applicants meant to do in the meantime? “Fuck off” seems to be the
message between the lines.</p>

<p>It makes me think about the 2023 layoffs back at Spotify. Under these new rules,
I could quite easily have been out on my arse there. If you’re here on a work
permit and get laid off, you have a few months to find a new job, but then
you’re outta here. Then what? Applying for a family visa back in the UK takes
months, and isn’t a sure thing. Some period of separation from my kids would be
all but guaranteed at that point. Life-changing emotional trauma. And for what?</p>

<p>There’s a certain smugness in the air these days in Sweden. The combination of
the Greenland-related threats by the Trump regime and ICE’s special military
operation in Minneapolis has a lot of people here comparing ourselves very
fucking favourably to the yanks right now. But at the same time, I keep reading
about teenagers being
<a href="https://www.thelocal.se/20221104/swedish-migration-does-not-care-that-my-son-has-never-been-to-iran">deported alone to countries they’ve never set foot in</a>,
and tiny kids who remind me of my own whose spirits seem completely crushed by
depression and anxiety from
<a href="https://www.svt.se/nyheter/inrikes/murad-har-bott-pa-atervandandecenter-i-tva-ar">living in hellish asylum return centres for years on end</a>.</p>

<p>When I wrote the other day about how I think
<a href="/2026/01/25/minneapolis-can-happen-in-malmo/">Minneapolis can happen in Malmö</a>,
I was in a way too generous. Ask the kids of the
<a href="https://nordic.ign.com/tom-clancys-the-division/102767/news/ubisoft-layoffs-continue-with-55-staff-at-the-division-studio-massive-and-ubisoft-stockholm-now-impa">game developer who’s just been laid off</a>
and has three months to get a new work permit before the family gets split up.
Ask the kids in the
<a href="https://www.svt.se/nyheter/lokalt/skane/kommunalradet-i-burlov-migrationsverket-skoter-inte-sitt-uppdrag">asylum return centre down the road in Burlöv</a>.
Ask
<a href="https://www.norran.se/english/engelska/artikel/family-forced-to-leave-sweden-so-many-think-this-is-wrong/jn1o5d4l">Parham Masoudi</a>.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[About six years ago I became a Swedish citizen. I’d been in the country around four years at the time. Being married to a Swedish citizen allowed it to happen early enough that Brexit never actually cost me my EU citizenship. Pretty nice! But if the new rules being introduced this year had applied back then I think I’d still be waiting today.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">The 2G and 3G network shutdown in Sweden is kind of a fiasco</title>
      <link href="https://henry.catalinismith.se/2026/01/28/the-2g-and-3g-network-shutdown-in-sweden-is-kind-of-a-fiasco/" rel="alternate" type="text/html" title="The 2G and 3G network shutdown in Sweden is kind of a fiasco" />
      <published>2026-01-28T00:00:00+01:00</published>
      <updated>2026-01-28T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/01/28/the-2g-and-3g-network-shutdown-in-sweden-is-kind-of-a-fiasco</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/01/28/the-2g-and-3g-network-shutdown-in-sweden-is-kind-of-a-fiasco/"><![CDATA[<p>My
<a href="/2025/08/20/notes-from-three-months-without-carrying-a-smartphone/">Nokia 2660</a>
is being forcibly disconnected next week due to the shutdown of the 2G and 3G
mobile networks in Sweden. It does support 4G VoLTE, but there’s some kind of
<a href="https://www.krisinformation.se/en/hazards-and-risks/disasters-and-incidents/2025/decommissioning-of-2g-and-3g-networks/">technicality about making emergency calls over 4G</a>
that the operator claims it falls short of compliance with.</p>

<figure>
 <img alt="Black flip phone on a sunlit wooden table" src="/2025/08/20/nokia@1024x768.webp" />
 <figcaption>Really gonna miss this thing. Fav phone I've had in years</figcaption>
</figure>

<p>Annoying, but seemed manageable. Except for
<a href="/2025/12/28/telia-is-holding-my-phone-number-hostage/">my own mistake of trying to switch to Telia</a>
this should have been easy to handle. Phone’s not going to work any more, so
replace it. Right?</p>

<p>Except <em>nobody seems to know</em> which phones will or won’t work. I’ve emailed the
manufacturer of the phone to ask them which of their other phones are unaffected
by the issue. Unhelpfully, they replied that my current phone is actually fine
and won’t be affected at all.</p>

<p>So I phoned my mobile operator’s customer support line and asked them. They
confirmed that my 2660 will definitely be disconnected next week. From this
conversation I was able to understand that no canonical list of supported and
unsupported phone models seems to exist anywhere in Sweden.</p>

<p>Post- och telestyrelsen are the authority behind the
<a href="https://pts.se/en/news-and-press-releases/operators-must-ensure-access-to-112--certain-mobile-phones-will-be-blocked/">mandate to disconnect my phone</a>.
Their stance is that it’s up to the operators to determine which phones are
affected. The operators, in turn, seem to be handling this on a best-effort
basis. It’s probably a low priority niche compared to the smartphone market.</p>

<p>Meanwhile, retailers continue to sell
<a href="https://cdon.se/produkt/smartphone-nokia-2660-flip-ds-bla-dubbel-sim-inbyggt-batteri-e25acb21d7355089/">handsets whose useful lifespan can be counted in days</a>
depending on which mobile network you’re subscribed to. None of them have even
added a notice to the product description. I have the impression that none of
them have noticed this is happening. Or the lack of information about which
phones are affected has made it impossible for them to act in advance.</p>

<p>Of the phone manufacturers I’ve emailed so far, nobody has owned up to their
phone being affected by the issue. Every reply I’ve received absolutely stank to
high heaven of unmoderated LLM output too. Who knows if I ever reached any
humans at all via that route.</p>

<p>The very helpful human I did speak to on my operator’s customer support line was
able to dig out some internal documentation identifying a few phones similar to
my current one that are <em>probably</em> not being disconnected next week. This was
very satisfying informtion to receive.</p>

<p>I immediately emailed the manufacturer of the phone to ask them about the
operator’s list of potentially supported phones. I got another very
ChatGPT-looking reply telling me <em>none</em> of those phones are supported, and that
“their team is working on it”. What the fuck?</p>

<p>For now I’ve given up and put my SIM back inside my old iPhone. I’m going to
wait until after the unsupported phones are locked out before I even think about
buying a replacement. Since nobody seems to agree with anybody else about which
phones will work, I need to be able to use ångerrätten to return any replacement
promptly if it doesn’t work.</p>

<p>The structural barriers to living without a smartphone in Sweden have proved
pretty overwhelming so far. Parking zones without physical parking meters.
Restaurants that only accept money via Swish, or where the only way to order is
via a QR code. Delivery companies that can’t deliver unless you have their app.</p>

<p>It has been a struggle, but this has been the closest I’ve come to feeling like
giving up on it.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[My Nokia 2660 is being forcibly disconnected next week due to the shutdown of the 2G and 3G mobile networks in Sweden. It does support 4G VoLTE, but there’s some kind of technicality about making emergency calls over 4G that the operator claims it falls short of compliance with.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/08/20/nokia@1024x768.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/08/20/nokia@1024x768.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">C# LSP in Helix on macOS</title>
      <link href="https://henry.catalinismith.se/2026/01/27/c-sharp-lsp-in-helix-on-macos/" rel="alternate" type="text/html" title="C# LSP in Helix on macOS" />
      <published>2026-01-27T00:00:00+01:00</published>
      <updated>2026-01-27T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/01/27/c-sharp-lsp-in-helix-on-macos</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/01/27/c-sharp-lsp-in-helix-on-macos/"><![CDATA[<p>Found myself writing C# in Helix on macOS a bit lately. Weird combination. It
took a moment to tweak things so that it all plays nice together.</p>

<figure>
 <img alt="Helix screenshot showing some C# code with the cursor on a Reverse() call and a popup window displaying the API documentation for Reverse()" src="/2026/01/27/lsp@1908x1424.webp" />
 <figcaption>
  Works great once it's configured!
 </figcaption>
</figure>

<p>To set up the actual SDK and the language server I did the following.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install dotnet
dotnet tool install --global csharp-ls
</code></pre></div></div>

<p>That puts the tools in place, but I found that running <code class="language-plaintext highlighter-rouge">hx --health c-sharp</code>
after this step revealed that Helix couldn’t find the <code class="language-plaintext highlighter-rouge">csharp-ls</code> binary. Adding
<code class="language-plaintext highlighter-rouge">~/.dotnet/tools</code> to my <code class="language-plaintext highlighter-rouge">$PATH</code> fixed it.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>set -U fish_user_paths $fish_user_paths ~/.dotnet/tools
</code></pre></div></div>

<p>From here I had a green checkmark in <code class="language-plaintext highlighter-rouge">hx --health c-sharp</code> but it still didn’t
quite work. Inspecting the language server logs in Helix with <code class="language-plaintext highlighter-rouge">:log-open</code>, I saw
that <code class="language-plaintext highlighter-rouge">csharp-ls</code> was unable to find the dotnet SDK. Setting the <code class="language-plaintext highlighter-rouge">DOTNET_ROOT</code>
environment variable in the language server config in
<code class="language-plaintext highlighter-rouge">~/.config/helix/languages.toml</code> fixed this.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[language-server.csharp]
command = "csharp-ls"
environment = { DOTNET_ROOT = "/opt/homebrew/Cellar/dotnet/10.0.102/libexec" }
</code></pre></div></div>

<p>This was everything. Annoying that C#’s devtools are so badly integrated with
each other and with macOS that you have to manually aim everything at each other
like that. But nothing too strenous.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[Found myself writing C# in Helix on macOS a bit lately. Weird combination. It took a moment to tweak things so that it all plays nice together.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/01/27/lsp@1908x1424.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/01/27/lsp@1908x1424.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Minneapolis can happen in Malmö</title>
      <link href="https://henry.catalinismith.se/2026/01/25/minneapolis-can-happen-in-malmo/" rel="alternate" type="text/html" title="Minneapolis can happen in Malmö" />
      <published>2026-01-25T00:00:00+01:00</published>
      <updated>2026-01-25T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/01/25/minneapolis-can-happen-in-malmo</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/01/25/minneapolis-can-happen-in-malmo/"><![CDATA[<p>The violent repression of the population of Minneapolis has been horrifying. It
feels unreal, like the news in an apocalyptic movie. And from the Swedish
perspective there’s a dangerous tendency towards exceptionalism that makes
people underestimate the potential for it to happen here.</p>

<p>Even a lot of yanks didn’t think it would get this bad this quick. Project 2025
laid the blueprints for Operation Metro Surge years ago now, but when it was
released, there was <em>tons</em> of denialism about the likelihood of it being
implemented.</p>

<p>So let’s take a look at the first four items in the Sweden Democrats’ document
“<a href="https://www.sd.se/wp-content/uploads/2023/08/atgardspaket-for-att-mota-angreppen-mot-sverige.pdf">Action plan to respond to attacks against Sweden</a>”
published around the same time period as Project 2025.</p>

<blockquote>
  <ol>
    <li>During demonstrations, riots and similar events, the police operate on the
basis of <em>Särskild polistaktik</em> (SPT). The concept is conflict reduction
rather than trying to control crowds. These tactics must change. The police
should be instructed to develop tactics based on taking and maintaining
control in difficult situations and combating serious attacks on law
enforcement, security and constitutionally protected rights.</li>
  </ol>
</blockquote>

<blockquote>
  <ol>
    <li>In order to take control of degenerate situations, new tools must be put
into use. The purchase and use of water cannons, rubber bullets and the
increased use of tear gas must be accelerated and financed.</li>
  </ol>
</blockquote>

<blockquote>
  <ol>
    <li>Uncertainty about a police officer’s right to use force to carry out their
mission risks leading the police force to back down from difficult
situations and numerical disadvantages. The police’s use of weapons is
currently regulated in the Police Act, as well as by delegation in the
Shooting Ordinance. Police officers also have the right to self-defense,
like everyone else. The Shooting Ordinance stipulates that police officers
may use weapons to arrest someone who is reasonably suspected of certain
crimes. The list of crimes is to be expanded to include sabotage of law
enforcement activities and violent riots.</li>
  </ol>
</blockquote>

<blockquote>
  <ol>
    <li>In addition to the police’s specially trained employees, the military also
has security expertise. Cooperation between the military and the police is
to be expanded so that the military can provide more assistance to law
enforcement with both personnel and equipment to handle difficult
situations.</li>
  </ol>
</blockquote>

<p>Long story short then, that’s less de-escalation, more use of force, and
increased militarisation. Remind you of anything in particular?</p>

<p>The party’s youth wing
<a href="https://omni.se/unga-sd-are-vill-se-svensk-motsvarighet-till-ice/a/gwVMG1">goes a step further and calls for a Swedish ICE</a>.
Doesn’t leave much room for doubt about which way the party’s ideological winds
are blowing in the long term.</p>

<p>I still remember how it felt
<a href="/2024/05/12/eurovision-week/">when Folkets Park was converted into a paramilitary fortress for Eurovision</a>.
The equipment and the will to deploy it already exist. All that’s missing is an
expansion of the criteria for when that should happen, and the Sweden Democrats
have set their vision out in clear terms.</p>

<figure>
  <img src="/2024/05/12/IMG_0157@1024x1024.webp" alt="Evening scene featuring a large black armored truck parked in a residential area." />
  <figcaption>
    Paramilitary hardware deployed by Swedish police outside Folkets Park. Photo from <a href="https://www.instagram.com/p/C6riMQNixQR/?img_index=1">Emma-Lina Johansson's Instagram</a>.
  </figcaption>
</figure>

<p>In fact, more recent history in Malmö even sets a precedent for an Operation
Metro Surge-style raid. On the 23rd of April last year,
<a href="https://www.svt.se/nyheter/lokalt/skane/stor-polisinsats-pa-rosengard-omradet-genomsoks">hundreds of police flooded Rosengård</a>
in an enormous high-visibility policing operation. It was a whole circus, with
<a href="https://www.tv4.se/artikel/7vza4RkQtpWV5FPwo7L9mE/stor-polisinsats-i-malmoe-genomsoeker-rosengard?utm_source=chatgpt.com">officials from Kronofogden and Skatteverket</a>
and god knows where else along for the ride. The pretext was to find weapons,
drugs, and arrest wanted criminals.</p>

<p><a href="https://www.sverigesradio.se/artikel/massiv-polisinsats-i-rosengard-gav-nastan-ingenting?utm_source=chatgpt.com">Approximately fuck all</a>
was actually achieved. No arrests, no big weapons stash discoveries, and some
small-scale confiscations of drugs. One thing that was certainly accomplished
though was the establishment of a precedent about large-scale police operations
in Malmö.</p>

<p>So I’m not just complacently thinking
“<a href="https://www.youtube.com/watch?v=FhQGFQoqBXU">the trouble with the fucking yanks is they’ve no fucking sense</a>”
to myself when I see videos of these far away street executions. I think about
how the raw materials for something very similar are right here in the city
around me.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[The violent repression of the population of Minneapolis has been horrifying. It feels unreal, like the news in an apocalyptic movie. And from the Swedish perspective there’s a dangerous tendency towards exceptionalism that makes people underestimate the potential for it to happen here.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/05/12/IMG_0157@1024x1024.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/05/12/IMG_0157@1024x1024.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">PICO-8 language server for Helix</title>
      <link href="https://henry.catalinismith.se/2026/01/24/pico-8-language-server-for-helix/" rel="alternate" type="text/html" title="PICO-8 language server for Helix" />
      <published>2026-01-24T00:00:00+01:00</published>
      <updated>2026-01-24T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/01/24/pico-8-language-server-for-helix</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/01/24/pico-8-language-server-for-helix/"><![CDATA[<p>Been working on this one PICO-8 game on and off for like five years now. Having
<a href="/2025/12/06/zellij-and-helix-together-might-actually-be-just-plain-better-than-neovim/">switched to Helix</a>
since the last time I touched it, I needed to set up the PICO-8 language server
for Helix first. It felt like nobody had glued together this combination of
tools before.</p>

<figure>
 <img alt="Google screenshot showing query 'PICO-8 language server for Helix' and result 'Your search - PICO-8 language server for Helix - did not match any documents.'" src="/2026/01/24/google@1400x654.webp" />
 <figcaption>
  Some of us are still out here Googling and understanding things before doing them
 </figcaption>
</figure>

<p>A
<a href="https://github.com/japhib/pico8-ls/issues/34#issuecomment-1413433466">very useful comment</a>
on a GitHub issue thread about Neovim integration was the magic ingredient.
After carrying out the suggested steps there and hooking it up inside
<code class="language-plaintext highlighter-rouge">~/.config/helix/languages.toml</code> I was in business.</p>

<figure>
 <img alt="Helix screenshot showing some PICO-8 Lua code with the cursor on a poke() call and a popup window displaying the API documentation for poke()" src="/2026/01/24/helix@1510x1504.webp" />
 <figcaption>
  Using the language server in the source code of my game <a href="/tailbone">Tailbone</a> to pull up the docs for <code><a href="https://pico-8.fandom.com/wiki/Poke">poke()</a></code>.
 </figcaption>
</figure>

<p>You can see my full dotfiles commit adding the necessary <code class="language-plaintext highlighter-rouge">languages.toml</code>
entries over on Codeberg at
<a href="https://codeberg.org/henrycatalinismith/dotfiles/commit/d7a2c218fabdedd263f3adfc2ff118c131b92133"><code class="language-plaintext highlighter-rouge">d7a2c21</code></a>.
In case Codeberg’s down (usually due to DDoS by crawlers belonging to AI
startups) the TOML is as follows.</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[[</span><span class="n">language</span><span class="k">]]</span>
<span class="n">name</span> <span class="o">=</span><span class="w"> </span><span class="s">"lua"</span>
<span class="n">file-types</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"p8"</span><span class="p">]</span>
<span class="n">language-servers</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"pico8-ls"</span><span class="p">]</span>

<span class="k">[</span><span class="n">language-server</span><span class="k">.</span><span class="n">pico8-ls</span><span class="k">]</span>
<span class="n">command</span> <span class="o">=</span><span class="w"> </span><span class="s">"pico8-ls"</span>
</code></pre></div></div>

<p>Gamedev in PICO-8 and text editing in Helix have a similar kind of vibe. It’s
the creative limitations thing I think. Being within a somewhat limited platform
encourages you to focus on your own creative output instead of becoming
distracted by the infinite possibilities of a perfectly flexible context.</p>

<p>And now hopefully that Google results page will be better for the next person
who comes along researching how to do this. Something tells me the intersection
of PICO-8 devs and Helix users is a crowd who are relatively more interested in
solving problems through research and understanding rather than vibecode
spray-and-pray so this feels like an appropriate kind of How I Fixed X blog post
for keeping the Old Ways alive.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      
        <category term="gamedev" />
      

      
      
        <summary type="html"><![CDATA[Been working on this one PICO-8 game on and off for like five years now. Having switched to Helix since the last time I touched it, I needed to set up the PICO-8 language server for Helix first. It felt like nobody had glued together this combination of tools before.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/01/24/google@1400x654.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/01/24/google@1400x654.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Ten Years of Sweden</title>
      <link href="https://henry.catalinismith.se/2026/01/22/ten-years-of-sweden/" rel="alternate" type="text/html" title="Ten Years of Sweden" />
      <published>2026-01-22T00:00:00+01:00</published>
      <updated>2026-01-22T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/01/22/ten-years-of-sweden</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/01/22/ten-years-of-sweden/"><![CDATA[<p>This year I’ll have been living in Sweden for ten years. That’s a full quarter
of my life. So when I found myself on a Stockholm trip this month I took the
opportunity to stay in the same hotel as I did the very first night I spent here
ten years ago.</p>

<p>On my first visit in 2016, the hotel check-in was one of my first ever
conversations in this country. I still remember how pathetically relieved I was
when they could speak English. On this visit in 2026, the hotel had installed a
touchscreen check-in kiosk to optimise away that human interaction. And funnily
enough it didn’t work, so I still got to check in the old fashioned way anyway.
Futuristic!</p>

<figure>
 <img alt="Back to the Future 2 scene in Cafe 80s where Marty tries to order a Pepsi from a video screen and two AI waiters begin to argue on it" src="/2026/01/22/ab@854x480.webp" />
 <figcaption>
  You are giving feedback on a new version of ChatGPT.
  <br />
  Which response do you prefer? Responses may take a moment to load.
 </figcaption>
</figure>

<p>In the six years since I got out of Stockholm, the tunnelbana’s added a mobile
ticket system. Mobile ticket adoption is damn near 100%, but I still like the
RFID travelcards. The Civil Defense Ministry should probably evaluate the impact
on Sweden’s crisis preparedness of those (American) phones becoming a single
point of failure for every facet of Swedish society. Queueing in Pressbyrån to
put credit on one of those cards makes me feel like a badly disguised time
traveler from the 2010s at this point.</p>

<p>Feeling out of touch in Stockholm makes me wonder how out of touch with life in
the UK I must have become by now. I got out just before the Brexit referendum.
Never experienced lockdown either. They’ve gotten through five different prime
ministers. And there’s a really bad homelessness crisis which means places I
used to hang out now have tents all over them.</p>

<p>A lot of that strikes me like the Wikipedia plot summary of a film I’ve never
seen (or maybe just
<a href="https://en.wikipedia.org/wiki/Children_of_Men#Plot">Children of Men</a>). The
island has stopped being “home” at an emotional level. If Reform take power in
the next election, further family visa restrictions are likely to close the door
permanently on any realistic prospect of return.</p>

<p>The barriers erected by Brexit sure haven’t helped. The reduced reliability of
mailing a gift. The increased rudeness of the airport passport cops. More than
that though has been the realisation that most of what I thought of as
“homesickness” was more like nostalgia for being 22 years old. I don’t
<em>actually</em> want to go to the pub that often now.</p>

<p>My Swedish has come a long way now too. Got to say though, nine times out of
ten, Swedish loses to English on expressiveness. Words like “availability” and
“accessibility” both being “tillgänglighet” means a room full of native Swedish
speakers will sometimes throw in the English translation to clarify.</p>

<p>One of the rare cases of Swedish having the richer vocabulary is “osolidarisk”.
That’s a negated adjective form of the word “solidarity”. Its general meaning is
“a person or action lacking solidarity” and it’s maddeningly difficult to
translate idiomatically without bulldozing the collective emphasis.</p>

<p>Take “Det är osolidariskt att köpa en Tesla under pågående strejk”. The best I
can do is “Buying a Tesla during the strike shows a lack of solidarity”. But
that shifts the whole centre of gravity of the sentence from the collective to
the individual. The Swedish sentence is a relational statement about group
cohesion. <em>You’re undermining our shared moral framework</em>. The English
translation waters it down to an individualistic warning about damaging your
personal brand. <em>What if someone thinks you’re a wanker?</em>. You’d have to double
the word count to avoid that problem.</p>

<p>At this point I’ve lived in Malmö longer than anywhere except the Wirral.
Another 15 years or so here and even that drops to second place. Meanwhile,
seeing the kids grow up here slowly paints over my genuine homesickness centered
around childhood memories of places in the Wirral.</p>

<p>For example, snow is rare enough in the UK that ownership of a sled is fairly
rare. We sure as hell didn’t have one in my family. So when we went sledding
down a fairly big hill a few weeks back, it wasn’t just the kids experiencing it
for the first time. It was fun enough that I might even be excited next time we
get heavy snow.</p>

<p>Stuff like that is magic for making a place feel like home.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[This year I’ll have been living in Sweden for ten years. That’s a full quarter of my life. So when I found myself on a Stockholm trip this month I took the opportunity to stay in the same hotel as I did the very first night I spent here ten years ago.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2026/01/22/ab@854x480.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2026/01/22/ab@854x480.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">“default_agent”: “plan”</title>
      <link href="https://henry.catalinismith.se/2026/01/17/default-agent-plan/" rel="alternate" type="text/html" title="“default_agent”: “plan”" />
      <published>2026-01-17T00:00:00+01:00</published>
      <updated>2026-01-17T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2026/01/17/default-agent-plan</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2026/01/17/default-agent-plan/"><![CDATA[<p>Pushed my first
<a href="https://codeberg.org/henrycatalinismith/dotfiles/commit/2bb8235bd47a6f8642e3cab6aef8478b6c28f88e">AI tool config</a>
to my dotfiles repo this week. It was config for OpenCode, and it looks like
this.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"$schema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://opencode.ai/config.json"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"default_agent"</span><span class="p">:</span><span class="w"> </span><span class="s2">"plan"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>All it does is make it start in plan mode instead of build mode. Doesn’t sound
much, but it’s transformed my relationship with the bloody thing.</p>

<p>I grew to really
<a href="/2025/03/14/survival-mode/">dislike those real-time typeahead code suggestions</a>.
That emphasis on adding new code embeds a junior approach into the very workflow
itself. Reckon they were an obvious net downgrade to my skills in hindsight, and
I regret the time wasted using them.</p>

<p>One of my fav games is NetHack, and it has this concept of items being blessed
or cursed. Blessed weapons do more damage. Cursed ones malfunction and can’t be
taken off. You pick up a weapon you found on the floor, and the game says “the
weapon welds itself to your hand”, and that’s when you realise you’re boned.</p>

<blockquote>
  <ul>
    <li>Anything that can be worn or wielded is reluctant to be taken off or put
aside if it’s cursed.</li>
    <li>Potions and scrolls generally work worse if they are cursed.</li>
    <li>Wands have a chance of exploding when you zap them while they are cursed.</li>
  </ul>

  <p><a href="https://nethackwiki.com/wiki/BUC#Cursed_items">https://nethackwiki.com/wiki/BUC#Cursed_items</a></p>
</blockquote>

<p>Those real-time AI code suggestions are a cursed NetHack item. They feel welded
to your cursor, so you’re stuck with them, and you’re worse off for it. I know
you <em>can</em> toggle them on and off, but I never did, and I don’t think I’ve ever
seen anyone else do it either.</p>

<p>OpenCode defaulting into plan mode is the opposite. It feels more like an actual
tool. Like you pick it up when you need it, and then you put it down when you’re
finished.</p>

<p>Been doing some C# at work lately. Not done much of that before, so it’s been a
learning process. One major barrier has been just not knowing the new vocabulary
for things. I’ve found that by cloning a bunch of our C# repos locally, I can
fire up OpenCode in plan mode and ask like “I have {problem}, look at {list of
repos} and tell me how they’re approaching it”, and a minute later I’m in
business.</p>

<p>Or I can ask it “Look at my unstaged changes and see if I’ve missed anything
obvious, or another approach that might achieve the same outcome in a better
way”. I particularly love that one cos shovelling out a bunch of pull requests
has never been the slow part of the process for me, whereas making things
<em>mergeable</em> sooner is moving the needle in a way that actually matters to me. I
know you can “assign to Copilot” on a PR too but I’m not convinced the noise it
generates is a net win for the review process.</p>

<p>So much of the hype about this tech focuses on code generation, yet so little of
the everyday value I’m finding in it is in that part of the process. No wonder
it’s been such a headfuck coming to terms with all these changes to the
industry. It’s been over 12 months since
<a href="/2024/11/12/using-ai-to-help-humans-translate-swedish-labour-laws/">the last time I wrote something positive about AI</a>.</p>

<p>There’s been a big round of new hype lately around things like the
<a href="https://github.com/torvalds/AudioNoise/commit/93a72563cba609a414297b558cb46ddd3ce9d6b5">Linus Antigravity commit</a>
and the <a href="https://antirez.com/news/158">Don’t fall into the anti-AI hype</a> post.
For the record, I think Antirez’s take that it’s “irrelevant, in the long run”
if the stock market crashes reminds me of the
<a href="https://www.youtube.com/watch?v=smoBjnTeSp4">Everything’s Fine sketch by Mitchell &amp; Webb</a>.
And I find his hand-wavy idea of voting away the social repercussions less
plausible than
<a href="/2025/10/20/rosa-luxemburg-predicted-the-ai-crash-in-1913/">Rosa Luxemburg’s take on the problem</a>.</p>

<p>But I am glad about my little bit of dotfiles repo JSON giving me a sense of
control over some tiny aspect of all this.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[Pushed my first AI tool config to my dotfiles repo this week. It was config for OpenCode, and it looks like this.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Efficiency My Arse</title>
      <link href="https://henry.catalinismith.se/2025/12/30/efficiency-my-arse/" rel="alternate" type="text/html" title="Efficiency My Arse" />
      <published>2025-12-30T00:00:00+01:00</published>
      <updated>2025-12-30T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/12/30/efficiency-my-arse</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/12/30/efficiency-my-arse/"><![CDATA[<p>Lately every time I read about AI it’s in the context of efficiency. Like
someone’s made another new button that lets you do a 10% worse job 10% faster
than before. And as there’s money to be made in this new trade-off we’re asked
to accept that it’s some kind of moral imperative to press that new button as
often as possible regardless of how contextually appropriate that trade-off is.
I’m not buying it.</p>

<p>An annoying problem I have is that I remember things. One thing I remember is
that five years ago, before all <em>this</em>, mob programming was all the rage. Under
that trend, we were asked to trade <em>away</em> efficiency in exchange for better
sociotechnical outcomes. The goal was knowledge sharing, better code, and a
shared sense of ownership.</p>

<p>I think my memory of this trend is still so vivid because it could be annoying
in its own way at times. For one, I’ve always been one of those irritating speed
demons who like to have three tasks on the go at once and use a million keyboard
shortcuts to avoid wasting a moment. Sitting and watching someone use their
mouse to select a line of code can admittedly challenge my patience.</p>

<p>Plus, it was super commonplace for mob programming to fail at its stated
objectives. Unless you’re utterly determined and meticulous about facilitating
it properly, it rapidly degenerates into a disfunctional dynamic where several
participants are sidelined for hours at a time as a passive audience to a
dominant in-group. That out-group’s engagement approaches zero. Paying the full
price of the increased engineering time and capturing almost none of the
sociotechnical upside was what opened my eyes to the reality of how much mob
programming was purely trend-driven. The technique had become an end in itself
and the outcomes were now secondary.</p>

<p>Now that the polar opposite – speed at the expense of understanding – has become
trendy all I see is a mirror image of the mob programming fad. It’s become so
trendy to chase efficiency using the one specific technique of generating code
using AI that the technique has become an end in itself. It doesn’t matter if
the efficiency goals are reached because the real driving force has become FOMO.</p>

<p>Another memory I’m sadly unable to shake is that the term “AI” used to refer to
an entire field of computer science that existed prior to ChatGPT. And apart
from large language models, AI’s other big contribution to society has been
recommendation systems.</p>

<p>So I think about all the hours each day that people spend scrolling TikTok, and
it makes the idea that AI is about efficiency difficult for me to swallow. What
exactly is so “efficient” about a social media addiction that swallows people’s
entire evenings? If AI is tricking people into living in a neverending lockdown
instead of going out and living meaningful lives, then is it even a net positive
for the economy? Or
<a href="https://mastodon.social/@yogthos@social.marxist.network/114447008062524394">does “the economy” really just mean “rich people’s bank balances”</a>?</p>

<p>From 9am to 5pm, AI is all about making you more efficient, and then from 5pm
until whenever you pass out on your sofa with your phone still illuminating your
sleeping face, it’s about making you do as little as possible. And because
there’s money to be made on both sides of that contradiction, we’re asked to
accept it as if it were a coherent plan for society.</p>

<p>All makes me wonder what lessons can I apply to the AI trend from any of this.
One from the mob programming trend is about that disfunctional in-group dynamic.
I think it arises so frequently because the people with the most social standing
within a given team tend to find themselves in the in-group where the
disfunction is less apparent. So I reckon the relative social status of each
organisation’s AI maximalists and skeptics probably has a big effect on what
working there looks like right now.</p>

<p>Another conclusion might just be that tech is incredibly trend-driven. That can
be pretty annoying and sometimes I’m even nostalgic for the love that went into
some of the detail work when TDD was trendy or when TypeScript was new and
people were still excited about it. But a possible upside is that, for all we
know, we might only be 18 months out from some YouTuber with a zillion followers
coining a hot new term for using a text editor to write code and flipping the
entire trend on its head.</p>

<p>Fingers crossed.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[Lately every time I read about AI it’s in the context of efficiency. Like someone’s made another new button that lets you do a 10% worse job 10% faster than before. And as there’s money to be made in this new trade-off we’re asked to accept that it’s some kind of moral imperative to press that new button as often as possible regardless of how contextually appropriate that trade-off is. I’m not buying it.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Telia is holding my phone number hostage</title>
      <link href="https://henry.catalinismith.se/2025/12/28/telia-is-holding-my-phone-number-hostage/" rel="alternate" type="text/html" title="Telia is holding my phone number hostage" />
      <published>2025-12-28T00:00:00+01:00</published>
      <updated>2025-12-28T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/12/28/telia-is-holding-my-phone-number-hostage</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/12/28/telia-is-holding-my-phone-number-hostage/"><![CDATA[<p>In late November I got an email from Comviq warning me that
<a href="/2025/08/20/notes-from-three-months-without-carrying-a-smartphone/">my beloved Nokia 2660</a>
will be blocked from their network in early 2026 due to the shutdown of their 2G
and 3G networks. As I’d only had the phone for about five months I decided it
was better to move to an operator whose network still supports it rather than
replace it. So I immediately started the process of moving my number to Telia.
Big mistake.</p>

<p>I’ve now been without my phone number for over three weeks. Here’s the timeline
of events.</p>

<table>
  <thead>
    <tr>
      <th>Date</th>
      <th>Event</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>2025-11-25</td>
      <td>Comviq warns of the <a href="https://www.comviq.se/nedslackning-2g-3g">2G/3G shutdown</a></td>
    </tr>
    <tr>
      <td>2025-11-25</td>
      <td>Order placed with Telia</td>
    </tr>
    <tr>
      <td>2025-12-11</td>
      <td>Service ends with Comviq</td>
    </tr>
    <tr>
      <td>2025-12-11</td>
      <td>Telia’s estimated delivery date</td>
    </tr>
    <tr>
      <td>2025-12-19</td>
      <td>Order cancelled with Telia</td>
    </tr>
    <tr>
      <td>2025-12-23</td>
      <td>Telia processes the cancellation</td>
    </tr>
    <tr>
      <td>2025-12-23</td>
      <td>Order placed with Comviq</td>
    </tr>
    <tr>
      <td>2025-12-23</td>
      <td>Comviq order automatically cancelled</td>
    </tr>
    <tr>
      <td>2025-12-23</td>
      <td>Phone call to Comviq support</td>
    </tr>
    <tr>
      <td>2025-12-23</td>
      <td>Phone call to Telia support</td>
    </tr>
    <tr>
      <td>2025-12-23</td>
      <td>Phone call to Comviq support</td>
    </tr>
    <tr>
      <td>2025-12-23</td>
      <td>Phone call to Telia support</td>
    </tr>
    <tr>
      <td>2025-12-23</td>
      <td>Telia registers a new order (why?)</td>
    </tr>
  </tbody>
</table>

<p>The delivery date for the Telia SIM came and went with no communication from
them about the delay. By the time I gave up and called to cancel on the 19th,
the estimated delivery date was over a week in the past. It had become unclear
if the SIM would ever arrive, and I realised I no longer wanted anything to do
with this operator.</p>

<p>Telia processed my order cancellation on the 23rd. I immediately tried to move
back to Comviq. That order failed, so I phoned Comviq to ask why. They said
Telia blocked them from moving the number, and that I needed to ask Telia why,
so I did. Telia said that the number was frozen, and to tell Comviq to reserve
it, so I did. Comviq said the number actually belonged to a company now, and to
ask Telia to make it a personal number, so I did. Telia said that the number was
actually still attached to the original cancelled order due to technical issues,
and to wait a few days for that to resolve, so I did.</p>

<p>After all that, Telia for some reason registered a brand new order for mobile
service. So now I’m waiting for the Christmas and New Year period to end so that
I can call them to start this entire process again from scratch.</p>

<blockquote>
  <p><strong>Update!</strong></p>

  <p>The brand new order is actually some kind of placeholder while my case is
debugged! I don’t need to start the entire process again from scratch!</p>
</blockquote>

<p>In the meantime, I’ve been forced into some kind of minor crisis scenario. I
have had to temporarily update my emergency contact number with schools to my
work phone number, as well as inform family and friends of the situation and the
temporary number. It’s impossible to know who or what has fallen through the
cracks. Services with a 2FA login flow based on SMS codes are inaccessible to
me. Delivery notifications via SMS? No chance. It has been a brand new kind of
Christmas stress.</p>

<p>I am done – forever – with Telia. If I have to fake my own death and re-enter
Sweden under a new identity to prevent them from locking me into this service
contract, then I will do so. If I can’t get my number back, and have to travel
to the headquarters of each company that uses SMS codes for 2FA in person with
my birth certificate and a urine sample to get my life back, then I will fucking
do so. Never been so utterly let down like this before by a phone company.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[In late November I got an email from Comviq warning me that my beloved Nokia 2660 will be blocked from their network in early 2026 due to the shutdown of their 2G and 3G networks. As I’d only had the phone for about five months I decided it was better to move to an operator whose network still supports it rather than replace it. So I immediately started the process of moving my number to Telia. Big mistake.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Lyme disease cured my caffeine addiction</title>
      <link href="https://henry.catalinismith.se/2025/12/16/lyme-disease-cured-my-caffeine-addiction/" rel="alternate" type="text/html" title="Lyme disease cured my caffeine addiction" />
      <published>2025-12-16T00:00:00+01:00</published>
      <updated>2025-12-16T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/12/16/lyme-disease-cured-my-caffeine-addiction</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/12/16/lyme-disease-cured-my-caffeine-addiction/"><![CDATA[<p>Had a real bastard of a headache the other day in Hemköp. So bad I had to leave
the store and go chill outside, away from the bright fluorescent lights. Almost
migraine territory.</p>

<p>Earlier that day I’d had one more coffee than usual to try to chase away a weird
stubborn tiredness. Naturally I blamed the coffee for the headache. This was
seriously some headache! It had been bad enough that I immediately decided to
quit coffee cold turkey.</p>

<p>For the next five or six days I soldiered through what I thought were caffeine
withdrawal symptoms. Started to get suspicious on about day five when it seemed
to be getting worse instead of better.</p>

<p>On day six of “caffeine withdrawal” a mild irritation I’d been ignoring on the
back of my right calf was suddenly not so mild any more. Upon inspection I found
a cool red bullseye pattern rash there. Wow, badass!</p>

<p>Ten minutes later I figured “Probably better Google that, actually”. And Google
said “That’s <em>Lyme disease</em>, softshite”. Within 24 hours of that I was
collecting a prescription for antibiotics.</p>

<p>Lyme disease is worth avoiding. One star. Brain fog is serious shit. While I was
in the waiting room at the doctors I had to really concentrate just to remember
where else I’d been that day.</p>

<p>Brain fog does pick up <em>some</em> points for tricking me into quitting coffee
though. Even after just two weeks clean I already felt better than I have in
years. Definitely keeping that.</p>

<p>Reminds me of my old Red Bull addiction in that respect. In the depths of the
sleep-deprived baby years I got mega addicted to that stuff. Like three or four
cans a day addicted.</p>

<p>Then in January 2023 the remote policy that my job depended seemed to go up in
smoke. They’d hired me in Malmö, but now I had to work in Stockholm, they said.
It was upsetting. I’d wake up every morning to such an immediate surge of
anxious adrenaline that I didn’t need the Red Bull any more. I was clean
forever, cold turkey, in the space of a week.</p>

<p>Have to say, out of the two, I’d take the Lyme disease any day. Antibiotics are
lot easier than gathering evidence for a potential 8 § MBL lawsuit and I doubt
I’ll have recurring nightmares about ticks the way I do about that RTO attempt.</p>

<p>Now that I’ve run out of stimulant addictions I’m excited to see what other
aspect of my lifestyle will be accidentally upgraded by my next mishap. Crossing
my fingers for accidentally becoming vegan as a result of my bike being stolen.</p>

<p>In the meantime, I’d rather not revisit either of these experiences. So I’ve
improved my habits around checking for ticks after being out in the woods. And
upon hearing about some Unionen clubs in the gamedev sector sending a joint
motion to Unionen about defending remote work, I gladly volunteered to help them
set up a bit of a web presence around the initiative.</p>

<p>So do go and check out <a href="https://remoteworks.se/">remoteworks.se</a>! And maybe
consider signing the petition too. Even if you’re in the same situation as I’m
in right now, where your current employer has great union collaboration and
remote work isn’t under immediate threat, it can be worth adding your voice to
this out of pure solidarity for others whose circumstances are more precarious.
And I can certainly tell you from experience that you never really know how
stable your own situation is until it isn’t any more!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[Had a real bastard of a headache the other day in Hemköp. So bad I had to leave the store and go chill outside, away from the bright fluorescent lights. Almost migraine territory.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">What Happens If You Press X In Helix In A Zellij Pane Running In Alacritty</title>
      <link href="https://henry.catalinismith.se/2025/12/12/what-happens-if-you-press-x-in-helix-in-a-zellij-pane-running-in-alacritty/" rel="alternate" type="text/html" title="What Happens If You Press X In Helix In A Zellij Pane Running In Alacritty" />
      <published>2025-12-12T00:00:00+01:00</published>
      <updated>2025-12-12T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/12/12/what-happens-if-you-press-x-in-helix-in-a-zellij-pane-running-in-alacritty</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/12/12/what-happens-if-you-press-x-in-helix-in-a-zellij-pane-running-in-alacritty/"><![CDATA[<p>One of the little things that inspired me to learn some Rust was the realisation
that suddenly all the tools I’m using every day are built with it. In
particular, Alacritty, Zellij and Helix are the bedrock of my workflow and
they’re all written in Rust.</p>

<p>It felt like an interesting Rust learning exerise to try to understand the
journey of a single keystroke through that stack. So iI have Helix running
inside a Zellij pane in an Alacritty window and I press <code class="language-plaintext highlighter-rouge">x</code> on my keyboard, what
kind of adventure does that keypress go on?</p>

<pre><code class="language-mermaid">flowchart LR
Alacritty --&gt; Zellij
Zellij --&gt; Helix
</code></pre>

<h2 id="alacritty">Alacritty</h2>

<p>The <a href="https://github.com/alacritty/alacritty/blob/18372031d1f1bda853b2027b7ef1c75b45942829/alacritty/src/event.rs#L1836"><code class="language-plaintext highlighter-rouge">handle_event()</code></a> function is the topmost entrypoint. Here the keypress
traverses a pair of nested <a href="https://doc.rust-lang.org/rust-by-example/flow_control/match.html"><code class="language-plaintext highlighter-rouge">match</code> blocks</a>. The outermost match block filters
by <a href="https://docs.rs/winit/0.30.12/winit/event/index.html"><code class="language-plaintext highlighter-rouge">winit::Event</code></a> type, of which a keypress is a <a href="https://docs.rs/winit/0.30.12/winit/event/enum.WindowEvent.html"><code class="language-plaintext highlighter-rouge">WindowEvent</code></a>. The
innermost block filters by the variant of <code class="language-plaintext highlighter-rouge">WindowEvent</code>, which in this case is
<a href="https://docs.rs/winit/0.30.12/winit/event/enum.WindowEvent.html#variant.KeyboardInput"><code class="language-plaintext highlighter-rouge">KeyboardInput</code></a>.</p>

<p>All that filtering leads to a call to <a href="https://github.com/alacritty/alacritty/blob/18372031d1f1bda853b2027b7ef1c75b45942829/alacritty/src/input/keyboard.rs#L22"><code class="language-plaintext highlighter-rouge">key_input()</code></a>. This is a wonderfully
readable function with nice little explanatory comments for each early return.
None of those guard clauses apply to our simple <code class="language-plaintext highlighter-rouge">x</code> keypress, which makes it all
the way to the <code class="language-plaintext highlighter-rouge">write_to_pty()</code> call at the very end.</p>

<p>That <a href="https://github.com/alacritty/alacritty/blob/18372031d1f1bda853b2027b7ef1c75b45942829/alacritty/src/event.rs#L687-L689"><code class="language-plaintext highlighter-rouge">write_to_pty()</code></a> call brings us right back to <code class="language-plaintext highlighter-rouge">event.rs</code>, where a
single-line function passes the value directly to a <code class="language-plaintext highlighter-rouge">notify()</code> call. I got a
little stuck here, because when you use <code class="language-plaintext highlighter-rouge">gd</code> to navigate to the definition of
the <code class="language-plaintext highlighter-rouge">notify()</code> function you end up in some kind of trait instead of the
implementation. Eventually I found my way past that by using <code class="language-plaintext highlighter-rouge">gr</code> on the trait
to list references to it and find the real implementation.</p>

<figure>
 <video poster="/2025/12/12/gdgr@1024x708.webp" src="/2025/12/12/gdgr@1024x706.mp4" title="Screen recording of Helix navigation workflow to bypass trait" controls="" preload="none" playsinline="">
 </video>
 <figcaption>
  Bypassing a trait to find the real implementation in Helix.
 </figcaption>
</figure>

<p>We land in a <a href="https://github.com/alacritty/alacritty/blob/18372031d1f1bda853b2027b7ef1c75b45942829/alacritty_terminal/src/event_loop.rs#L335-L347"><code class="language-plaintext highlighter-rouge">notify()</code></a> implementation inside a struct that contains an
<code class="language-plaintext highlighter-rouge">EventLoopSender</code>, which is a pretty big clue as to what happens next. Our
keypress becomes a <code class="language-plaintext highlighter-rouge">Msg::Input</code> on the event loop as a result of the code in
<code class="language-plaintext highlighter-rouge">notify()</code>.</p>

<p>That <code class="language-plaintext highlighter-rouge">Msg::Input</code> is read back out of the event loop in
<a href="https://github.com/alacritty/alacritty/blob/18372031d1f1bda853b2027b7ef1c75b45942829/alacritty_terminal/src/event_loop.rs#L91"><code class="language-plaintext highlighter-rouge">drain_recv_channel()</code></a>. It puts it into a write queue called <a href="https://github.com/alacritty/alacritty/blob/18372031d1f1bda853b2027b7ef1c75b45942829/alacritty_terminal/src/event_loop.rs#L401"><code class="language-plaintext highlighter-rouge">write_list</code></a>.
Later a <a href="https://github.com/alacritty/alacritty/blob/18372031d1f1bda853b2027b7ef1c75b45942829/alacritty_terminal/src/event_loop.rs#L415-L417"><code class="language-plaintext highlighter-rouge">goto_next()</code></a> call moves it into a value called <a href="https://github.com/alacritty/alacritty/blob/18372031d1f1bda853b2027b7ef1c75b45942829/alacritty_terminal/src/event_loop.rs#L402"><code class="language-plaintext highlighter-rouge">writing</code></a>. As a
result of this, the subsequent call to <a href="https://github.com/alacritty/alacritty/blob/18372031d1f1bda853b2027b7ef1c75b45942829/alacritty_terminal/src/event_loop.rs#L174"><code class="language-plaintext highlighter-rouge">pty_write()</code></a> sends the keypress data
to <code class="language-plaintext highlighter-rouge">pty.writer().write()</code>.</p>

<p>That final <a href="https://doc.rust-lang.org/stable/std/fs/fn.write.html"><code class="language-plaintext highlighter-rouge">write()</code></a> call is happening on a <a href="https://doc.rust-lang.org/stable/std/fs/struct.File.html"><code class="language-plaintext highlighter-rouge">std::fs::File</code></a>. We’re at the
boundary between Alacritty and Zellij here, where data changes hands by writing
to virtual files in <code class="language-plaintext highlighter-rouge">/dev</code>. Alacritty writes the data to the master PTY file
descriptor and from there it’s Zellij’s problem.</p>

<h2 id="zellij">Zellij</h2>

<p>The second leg of the keypress’s journey begins in Zellij’s <a href="https://github.com/zellij-org/zellij/blob/b52f96c9460aa53c286bf313754b3db67c1d0033/zellij-client/src/stdin_handler.rs#L23"><code class="language-plaintext highlighter-rouge">stdin_loop()</code></a>
function. This reads the data written by Alacritty and turns it into an
<code class="language-plaintext highlighter-rouge">InputInstruction::KeyEvent</code>. A call to <code class="language-plaintext highlighter-rouge">send_input_instructions.send()</code> hands
the event off to the input handler.</p>

<p>Zellij uses a message passing crate called <a href="https://crates.io/crates/crossbeam-channel"><code class="language-plaintext highlighter-rouge">crossbeam-channel</code></a> for this step.
That <code class="language-plaintext highlighter-rouge">send_input_instructions</code> struct is a <a href="https://docs.rs/crossbeam-channel/latest/crossbeam_channel/struct.Sender.html"><code class="language-plaintext highlighter-rouge">crossbeam_channel::Sender</code></a>. And
there’s a <a href="https://docs.rs/crossbeam-channel/latest/crossbeam_channel/struct.Receiver.html"><code class="language-plaintext highlighter-rouge">crossbeam_channel::Receiver</code></a> struct called
<code class="language-plaintext highlighter-rouge">receive_input_instructions</code> in <a href="https://github.com/zellij-org/zellij/blob/b52f96c9460aa53c286bf313754b3db67c1d0033/zellij-client/src/input_handler.rs#L148"><code class="language-plaintext highlighter-rouge">handle_input()</code></a> which receives the message
and passes the raw bytes into a call to <a href="https://github.com/zellij-org/zellij/blob/b52f96c9460aa53c286bf313754b3db67c1d0033/zellij-client/src/input_handler.rs#L262-L275"><code class="language-plaintext highlighter-rouge">handle_key()</code></a>.</p>

<p>Those raw bytes are repackaged once again as a <code class="language-plaintext highlighter-rouge">ClientToServerMsg::Key</code>, which
is a big clue about what happens next. Zellij has a client-server architecture,
and up to now we’ve been in the client part. Inside <a href="https://github.com/zellij-org/zellij/blob/b52f96c9460aa53c286bf313754b3db67c1d0033/zellij-server/src/route.rs#L1548"><code class="language-plaintext highlighter-rouge">route_thread_main()</code></a>, the
server receives the <code class="language-plaintext highlighter-rouge">ClientToServerMsg::Key</code> and passes it to <a href="https://github.com/zellij-org/zellij/blob/b52f96c9460aa53c286bf313754b3db67c1d0033/zellij-server/src/route.rs#L160"><code class="language-plaintext highlighter-rouge">route_action()</code></a>
as an <code class="language-plaintext highlighter-rouge">Action::Write</code>.</p>

<p>From there it’s converted to a <code class="language-plaintext highlighter-rouge">ScreenInstruction::WriteCharacter</code> and passed to
<code class="language-plaintext highlighter-rouge">send_to_screen()</code>. That’s picked up by <a href="https://github.com/zellij-org/zellij/blob/b52f96c9460aa53c286bf313754b3db67c1d0033/zellij-server/src/screen.rs#L3554"><code class="language-plaintext highlighter-rouge">screen_thread_main()</code></a> which sends it
onwards to <a href="https://github.com/zellij-org/zellij/blob/b52f96c9460aa53c286bf313754b3db67c1d0033/zellij-server/src/tab/mod.rs#L2441"><code class="language-plaintext highlighter-rouge">tab.write_to_active_terminal()</code></a>.</p>

<p>Zellij then figures out the ID of the active pane and sends the keypress to it
by calling <a href="https://github.com/zellij-org/zellij/blob/b52f96c9460aa53c286bf313754b3db67c1d0033/zellij-server/src/tab/mod.rs#L2515"><code class="language-plaintext highlighter-rouge">write_to_pane_id()</code></a>. There, it becomes a
<code class="language-plaintext highlighter-rouge">PtyWriteInstruction::Write</code> and makes its third crossbeam sender/receiver hop
via <code class="language-plaintext highlighter-rouge">send_to_pty_writer()</code> into <a href="https://github.com/zellij-org/zellij/blob/main/zellij-server/src/pty_writer.rs"><code class="language-plaintext highlighter-rouge">pty_writer_main()</code></a>.</p>

<p>That forwards it on to <a href="https://github.com/zellij-org/zellij/blob/b52f96c9460aa53c286bf313754b3db67c1d0033/zellij-server/src/os_input_output.rs#L663"><code class="language-plaintext highlighter-rouge">write_to_tty_stdin()</code></a>, which figures out the right
file descriptor for the terminal ID and uses <a href="https://docs.rs/nix/latest/nix/unistd/fn.write.html"><code class="language-plaintext highlighter-rouge">nix::unistd::write()</code></a> to write
the data to it. We’re writing to a file again, which means we’re at the border
between two programs. It’s the end of the Zellij leg of the journey, and the
keypress is about to enter Helix.</p>

<h2 id="helix">Helix</h2>

<p>In Helix, a function called <a href="https://github.com/helix-editor/helix/blob/27e8bdb2f1ea83a0aefdb103cce662cb78ed55c2/helix-term/src/application.rs#L319"><code class="language-plaintext highlighter-rouge">event_loop_until_idle()</code></a> polls for events from
<code class="language-plaintext highlighter-rouge">stdin</code> and sends them to <a href="https://github.com/helix-editor/helix/blob/27e8bdb2f1ea83a0aefdb103cce662cb78ed55c2/helix-term/src/application.rs#L688"><code class="language-plaintext highlighter-rouge">handle_terminal_events()</code></a>.</p>

<p>An <code class="language-plaintext highlighter-rouge">into()</code> call on the event takes us on a detour via a <a href="https://doc.rust-lang.org/rust-by-example/conversion/from_into.html"><code class="language-plaintext highlighter-rouge">From</code> trait</a>’s
<a href="https://github.com/helix-editor/helix/blob/27e8bdb2f1ea83a0aefdb103cce662cb78ed55c2/helix-view/src/input.rs#L462-L476"><code class="language-plaintext highlighter-rouge">from()</code></a> function to convert from a <a href="https://docs.rs/termina/latest/termina/event/struct.KeyEvent.html"><code class="language-plaintext highlighter-rouge">termina::Event::KeyEvent</code></a> to Helix’s
own internal <code class="language-plaintext highlighter-rouge">Event::Key</code> struct. That Helix event then goes into a
<a href="https://github.com/helix-editor/helix/blob/27e8bdb2f1ea83a0aefdb103cce662cb78ed55c2/helix-term/src/compositor.rs#L144"><code class="language-plaintext highlighter-rouge">handle_event()</code></a>
function which uses event bubbling to route it to the right part of Helix.</p>

<p>In the case of our <code class="language-plaintext highlighter-rouge">x</code> keypress, with Helix in insert mode, the right part of
Helix is the editor. The editor’s own
<a href="https://github.com/helix-editor/helix/blob/27e8bdb2f1ea83a0aefdb103cce662cb78ed55c2/helix-term/src/ui/editor.rs#L1358"><code class="language-plaintext highlighter-rouge">handle_event()</code></a>
function routes the event to the <a href="https://github.com/helix-editor/helix/blob/27e8bdb2f1ea83a0aefdb103cce662cb78ed55c2/helix-term/src/ui/editor.rs#L916"><code class="language-plaintext highlighter-rouge">insert_mode()</code></a> function, which passes it on
to a function called <a href="https://github.com/helix-editor/helix/blob/27e8bdb2f1ea83a0aefdb103cce662cb78ed55c2/helix-term/src/commands.rs#L4174"><code class="language-plaintext highlighter-rouge">insert_char()</code></a>.</p>

<p>The event then becomes a transaction which is applied to the document. The <code class="language-plaintext highlighter-rouge">x</code>
character from the keypress is now a part of the document but it’s not yet
visible on the screen. The successful consumption of the event sets in motion a
render within Helix which will work its way back via Zellij to Alacritty.</p>

<p>I’m too exhausted from following the keypress down the chain to be arsed
following it back up. This was a fun exercise though. I learned a lot about
Rust, which was the point, but also about systems programming, which was
unintentional.</p>

<p>All three of these are tools I expect to use daily for a long time to come too,
so it feels really worthwhile to work towards understanding what makes them
tick. And Rust makes that a lot more feasible for me. I like C a lot, and
remember reading The C Programming Language very fondly, but I’ve always really
struggled to read real-world C code. It’s fun finally being able to understand
stuff at this level.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[One of the little things that inspired me to learn some Rust was the realisation that suddenly all the tools I’m using every day are built with it. In particular, Alacritty, Zellij and Helix are the bedrock of my workflow and they’re all written in Rust.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/12/12/gdgr@1024x708.webp%20What%20happens%20if%20you%20press%20&apos;x&apos;%20in%20Helix%20in%20a%20Zellij%20pane%20running%20in%20Alacritty" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/12/12/gdgr@1024x708.webp%20What%20happens%20if%20you%20press%20&apos;x&apos;%20in%20Helix%20in%20a%20Zellij%20pane%20running%20in%20Alacritty" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">lazyrename for Helix and Zellij</title>
      <link href="https://henry.catalinismith.se/2025/12/07/lazyrename-for-helix-and-zellij/" rel="alternate" type="text/html" title="lazyrename for Helix and Zellij" />
      <published>2025-12-07T00:00:00+01:00</published>
      <updated>2025-12-07T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/12/07/lazyrename-for-helix-and-zellij</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/12/07/lazyrename-for-helix-and-zellij/"><![CDATA[<p>Earlier today I finally expelled
<a href="/2025/12/06/zellij-and-helix-together-might-actually-be-just-plain-better-than-neovim/">a happy rant about Helix and Zellij</a>
that’s been building up inside of me for weeks. And the instant it was out of me
I was itching to get the ball rolling on a tool I’ve been needing in this cosy
little ecosystem. In a few stolen moments of coding here and there throughout
the day I’ve managed to implement a rudimentary 0.0.0 version of a new tool I’m
calling <a href="https://codeberg.org/henrycatalinismith/lazyrename/">lazyrename</a>.</p>

<p>Renaming files is something the mainstream editors like VSCode have put a lot of
love into. If your new filename has stuff in it like spaces or funky punctuation
it’s handled for you. If the destination path includes non-existent directories
they’re created automatically. And once the new file’s created the editor opens
it immediately. This kind of polish is valuable when you’re arsehole deep in a
really hard problem and you just need to move some files around to get to the
next step, and it’s been missing from Helix.</p>

<p>So lazyrename’s purpose is to add that little bit of extra polish. It spawns
inside a Zellij pane where you simply edit the relative filename of the current
buffer and hit enter when you’re done. Then it performs any necessary file
system operations, closes the pane, and re-opens your Helix buffer at the new
path. Looks like this.</p>

<figure>
 <video poster="/2025/12/07/demo@1024x722.webp" src="/2025/12/07/demo@1024x722.mp4" title="Terminal screenshot showing lazyrename inside Helix" controls="" preload="none" playsinline="">
 </video>
</figure>

<p>I’ve been increasingly interested in Rust lately and have found myself watching
more and more videos about it. This project was my first time really trying to
build something with the language and I had an absolute shitload of fun with it.</p>

<p>It would be interesting to find out if this tool is of interest to others in the
Helix community so do please feel free to share this post or
<a href="https://codeberg.org/henrycatalinismith/lazyrename/">the Codeberg repo</a>
wherever you think people might find it interesting. There’s probably more
development potential in the project such as maybe using the LSP server to
update any references to the old filename in the repo so let’s find out if this
is a “one and done” project or something with a little bit of legs!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[Earlier today I finally expelled a happy rant about Helix and Zellij that’s been building up inside of me for weeks. And the instant it was out of me I was itching to get the ball rolling on a tool I’ve been needing in this cosy little ecosystem. In a few stolen moments of coding here and there throughout the day I’ve managed to implement a rudimentary 0.0.0 version of a new tool I’m calling lazyrename.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/12/07/demo@1024x722.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/12/07/demo@1024x722.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Zellij and Helix together might actually be just plain better than Neovim</title>
      <link href="https://henry.catalinismith.se/2025/12/06/zellij-and-helix-together-might-actually-be-just-plain-better-than-neovim/" rel="alternate" type="text/html" title="Zellij and Helix together might actually be just plain better than Neovim" />
      <published>2025-12-06T00:00:00+01:00</published>
      <updated>2025-12-06T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/12/06/zellij-and-helix-together-might-actually-be-just-plain-better-than-neovim</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/12/06/zellij-and-helix-together-might-actually-be-just-plain-better-than-neovim/"><![CDATA[<p>For starters Helix is a masterpiece. Imagine Neovim with
<a href="https://github.com/nvim-telescope/telescope.nvim">telescope</a>,
<a href="https://github.com/neovim/nvim-lspconfig">lspconfig</a>,
<a href="https://github.com/nvim-telescope/telescope.nvim">lspsaga</a>,
<a href="https://github.com/lewis6991/gitsigns.nvim">gitsigns</a>, and
<a href="https://github.com/folke/which-key.nvim">which-key</a> all built-in. It’s a
finished piece of software as far as I’m concerned. The maintainers could
permanently feature freeze it today and I’d be happy. They’re shipping a plugin
engine soon and I almost wish they wouldn’t.</p>

<p>The reason I find the prospect of a plugin engine in Helix almost unnecessary is
that Zellij already acts as a limited plugin API. It’s limited but it’s enough.
By configuring keyboard shortcuts to fire up terminal utilities against the
current file in a Zellij pane you can easily streamline any workflows that would
otherwise require context switching into a shell.</p>

<p>For most of my career my workflow to read the commit log for a file has been to
<a href="/2025/11/27/copy-relative-path-to-file-in-helix/">copy its relative path to the clipboard</a>
and then paste that into a terminal command. With Zellij and Helix I can
automate that with config like this.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[keys.normal.backspace]
h = ":sh zellij run --close-on-exit -f -x=1%% -y=4%% --width=99%% --height=92%% -- lazygit --filter \"%{buffer_name}\""
</code></pre></div></div>

<p>This enables me to invoke that same workflow with two keystrokes. Then I can
browse the history and when I’m finished, closing it drops me right back where I
was. I get to keep the exact same terminal-driven workflow as before and simply
upgrade its efficiency. Looks like this.</p>

<figure>
 <video poster="/2025/12/06/lazygit-filename@1024x598.webp" src="/2025/12/06/lazygit-filename@1024x598.mp4" title="Terminal screenshot showing lazygit inside a Zellij pane" controls="" preload="none" playsinline="">
 </video>
</figure>

<p>So you have a core editor that’s ready to use out of the box. And a terminal
workspace that makes it easy to integrate that editor with other terminal
software.</p>

<p>I think this is arguably superior to Neovim’s excellent Lua plugin API and
ecosystem. The editor ships as a coherent finished product instead of a DIY kit.
Then the extensibility that does exist in the form of the Zellij magic is there
for streamlining integrations with external tools rather than for finishing off
an incomplete core editor.</p>

<p>Another thing I love about Helix is that the selection first approach to editing
makes it easier to visually follow what’s going on. This is a lovely boost to
pair- or mob-programming sessions where the relative suddenness of Vim’s editing
rhythm easily leaves collaborators feeling excluded and confused by what’s
happening on the screen. The same is true of Helix’s quick reference, which
means many keyboard shortcuts will be visually explained on the screen
immediately before they’re invoked.</p>

<p>Integrating tools like lazygit is a natural extension of this inclusiveness.
<em>Loads</em> of people use lazygit these days. Building workflows around well-known
tools like these instead of esoteric editor-specific plugins is a small but
meaningful opportunity to be a more inclusive collaborator.</p>

<p>I’m very happy with this setup. It’s especially exciting that it’s starting to
inspire me with little ideas for self-contained tools that would work well
within this workflow but don’t yet exist. I hope I actually get around to
building one or two of them.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[For starters Helix is a masterpiece. Imagine Neovim with telescope, lspconfig, lspsaga, gitsigns, and which-key all built-in. It’s a finished piece of software as far as I’m concerned. The maintainers could permanently feature freeze it today and I’d be happy. They’re shipping a plugin engine soon and I almost wish they wouldn’t.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/12/06/lazygit-filename@1024x598.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/12/06/lazygit-filename@1024x598.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Copy relative path to file in Helix</title>
      <link href="https://henry.catalinismith.se/2025/11/27/copy-relative-path-to-file-in-helix/" rel="alternate" type="text/html" title="Copy relative path to file in Helix" />
      <published>2025-11-27T00:00:00+01:00</published>
      <updated>2025-11-27T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/11/27/copy-relative-path-to-file-in-helix</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/11/27/copy-relative-path-to-file-in-helix/"><![CDATA[<p>In recent weeks I’ve accidentally migrated from Neovim to Helix. I say
“accidentally” because I wasn’t really intending to switch. There was a
<a href="https://lobste.rs/s/rahklm/notes_on_switching_helix_from_vim">post on Lobste.rs called “Notes on switching to Helix from vim”</a>
that I read about a month ago, which inspired me to give Helix a bit of a try.
My expectation had been that it’d be an interesting experiment but instead I
found myself choosing to use Helix all the time.</p>

<p>I just love how snappy and minimalist it is. Helix has everything I need and
nothing I don’t. For example, while I really like the “added, removed and
updated” lines thing that it has, I’ve never gotten along with in-depth VCS
integrations relating to e.g. browsing a file’s history within text editors or
plugins. For that kind of stuff I’ve always preferred to just work in the
terminal itself so Helix’s plus/minus/delta colours in the gutter are all I want
from it.</p>

<p>That being said, the one missing step is the ability to copy the current file’s
relative path to the clipboard so that it can be pasted into some git command in
another tab. This proved possible to implement as a keybinding as follows.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[keys.normal.space.z]
p = ":sh echo \"%{buffer_name}\" | pbcopy"
</code></pre></div></div>

<p>I’m using “space z” as a prefix because Helix’s default keybindings already use
almost the entire keyboard except for that. So for me “space z” is like a
personal namespace for any custom keybindings where I can pick any letter I want
without conflicting.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[In recent weeks I’ve accidentally migrated from Neovim to Helix. I say “accidentally” because I wasn’t really intending to switch. There was a post on Lobste.rs called “Notes on switching to Helix from vim” that I read about a month ago, which inspired me to give Helix a bit of a try. My expectation had been that it’d be an interesting experiment but instead I found myself choosing to use Helix all the time.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Coders &amp;amp; Organizers 2025</title>
      <link href="https://henry.catalinismith.se/2025/11/24/coders-organizers-2025/" rel="alternate" type="text/html" title="Coders &amp;amp; Organizers 2025" />
      <published>2025-11-24T00:00:00+01:00</published>
      <updated>2025-11-24T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/11/24/coders-organizers-2025</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/11/24/coders-organizers-2025/"><![CDATA[<p>Back down to earth with a bump today after an amazing weekend attending the
Coders &amp; Organizers meetup here in Malmö. Difficult to readjust back to everyday
life after such an inspiring and productive experience.</p>

<p>We heard incredibly cool insights from some of the people behind the recent
political earthquakes in New York and Copenhagen, as well as ideas from people
working to bring about similar changes elsewhere. And we worked together on
creating the tools to deliver on those strategies.</p>

<p>I don’t have the numbers but I think this event was something like double the
size of any Zetkin-related event I’ve attended previously. And I’ve noticed that
one of the reasons to look forward to these events is now that it’s become an
opportunity to catch up with friends within the movement from all over the
place. It’s growing. <em>We’re</em> growing.</p>

<p>2026 is an election year in Sweden and I’m excited because all around me I see a
movement with energy and ideas and the courage to combine the two into action.
It’s not just at the big events either. Every time I make a quick trip to the
store to stock up on bread or eggs or whatever and see someone’s covered up some
horrible zionist graffiti along my route that sense of a movement on the rise
grows stronger.</p>

<p>A couple of new ideas from the weekend really stuck with me. One was from the
DSA talk, and was about how the majority of their door-knocking campaign is
organised by volunteers who graduate from just knocking on doors to a field lead
role, where they coordinate a whole team of door knockers. Then in a later talk
I became really fixated on an idea I heard about how volunteer time is an even
more precious resource than money.</p>

<p>The penny dropped when the link between those two ideas hit me. When volunteers
are doing the organising, the efficiency of the organiser tooling itself becomes
critical. So each seemingly tiny bugfix or workflow optimisation in the Zetkin
software platform can have counter-intuitively huge upsides for campaign
resources.</p>

<p>A lot clicked for me this weekend about the importance of Zetkin and the power
of this community. Excited to see what 2026 brings. You can join the
<a href="https://app.zetkin.org/o/433/events/45670">next hackathon on December 7th</a>
remotely if you’re curious about getting involved.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="zetkin" />
      

      
      
        <summary type="html"><![CDATA[Back down to earth with a bump today after an amazing weekend attending the Coders &amp; Organizers meetup here in Malmö. Difficult to readjust back to everyday life after such an inspiring and productive experience.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Zellij Cheatsheet</title>
      <link href="https://henry.catalinismith.se/2025/11/21/zellij-cheatsheet/" rel="alternate" type="text/html" title="Zellij Cheatsheet" />
      <published>2025-11-21T00:00:00+01:00</published>
      <updated>2025-11-21T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/11/21/zellij-cheatsheet</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/11/21/zellij-cheatsheet/"><![CDATA[<p>Over the last couple of months <a href="https://zellij.dev/">Zellij</a> has become one of
my favourite everyday tools. I’m at a point now where I don’t want to be
configuring stuff with millions of custom keybindings. Instead I want something
that works already out of the box. Zellij is that.</p>

<p>To help me learn Zellij’s default keybindings I created this printable
cheatsheet. I keep a printed copy on my desk to look stuff up quickly when I
need it.</p>

<p><a href="/2025/11/21/zellij.html"><img src="/2025/11/21/screenshot@2880x2310.webp" alt="A4 paper with several blocks of structured content" /></a></p>

<p>It’s available as <a href="/2025/11/21/zellij.html">HTML</a>, <a href="/2025/11/21/zellij@1583x1916.webp">PNG</a>
and <a href="/2025/11/21/zellij.pdf">PDF</a>.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[Over the last couple of months Zellij has become one of my favourite everyday tools. I’m at a point now where I don’t want to be configuring stuff with millions of custom keybindings. Instead I want something that works already out of the box. Zellij is that.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/11/21/screenshot@2880x2310.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/11/21/screenshot@2880x2310.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Give me lava chicken or give me death</title>
      <link href="https://henry.catalinismith.se/2025/11/17/give-me-lava-chicken-or-give-me-death/" rel="alternate" type="text/html" title="Give me lava chicken or give me death" />
      <published>2025-11-17T00:00:00+01:00</published>
      <updated>2025-11-17T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/11/17/give-me-lava-chicken-or-give-me-death</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/11/17/give-me-lava-chicken-or-give-me-death/"><![CDATA[<p>Rasmus Fleischer made an interesting appeal earlier today.</p>

<blockquote>
  <p>Allt jag önskar mig till jul, eller snarare till första advent: att ni alla
kan motstå frestelsen att dela er “Spotify Wrapped”.</p>

  <p>I stället föreslår jag att ni delar var sin personlig reflexion kring ert
musiklyssnande 2025: hur ni lyssnat, på vilken musik, vad som faktiskt har
berört er.</p>

  <p><a href="https://mastodon.social/@rasmusfleischer@mastodon.nu/115564010681509413">@rasmusfleischer@mastodon.nu/115564010681509413</a></p>
</blockquote>

<p>He’s saying why not share a personal reflection about your relationship with
music in 2025 instead of just posting data visualisation screenshots from a
bloody app. I sympathise with the sentiment here. Here’s a rough draft of one
for me.</p>

<p>One of the kids got a cassette player for his birthday and listened to the
Aristocats soundtrack so much on it that the tape wore out and broke. Then we
miraculously found a vinyl copy of it for 10kr in a second hand shop. It’s
simultaneously the cheapest and one of the most joyful things we own. Also its
cover has the same colour scheme as Never Mind The Bollocks so it’s easy to
confuse the two when browsing. An object of considerable psychic power in other
words.</p>

<p><img src="/2025/11/17/aristocats@600x596.webp" alt="Yellow vinyl sleeve with Aristocats in purple and the characters below" /></p>

<p>The headlines about Bob Vylan’s Glastonbury set led me to discover my new
favourite band. I fell in love with their entire body of work overnight and have
spent a lot of time listening to it while removing zionist graffiti from my
local neighbourhood. Check out
<a href="https://www.youtube.com/watch?v=urV4yjHUBIw">Pretty Songs</a> or
<a href="https://www.youtube.com/watch?v=VFqhJyvly1g">We Live Here</a>.</p>

<p>For a few weeks I got super into the management simulation game Factorio. I was
learning how to automate
<a href="https://wiki.factorio.com/Chemical_science_pack">blue science</a> when it hit me
that I could have learned an entire musical instrument for less effort than
this. Since then I’ve amassed three harmonicas and half filled a
<a href="https://www.moleskine.com/en-se/shop/notebooks/art-collection/music-notebooks/music-notebook-large-black-9788862933100.html">Moleskine music notebook</a>.</p>

<p><img src="/2025/11/17/route4@1024x940.webp" alt="Music notebook open to a page titled Route 4 with 3 lines of musical notation with tabs" /></p>

<p>You’re meant to play blues on harmonica mainly I think, but so far my fav shit
to play is the Pokémon Red/Blue soundtrack. Apart from nostalgia I think a big
part of that preference comes from the fact that tabs were impossible to find
for much of it, so I often had to transpose sheet music arranged for other
instruments.
<a href="https://phirephoenix.com/blog/2025-10-11/friction">Friction gave it meaning</a>.
Haven’t touched Factorio since BTW.</p>

<p>After some considerable stubborn resistance on my part, my very patient wife
convinced me to watch a documentary about some boring old Swedish punk band I
didn’t really want to hear about. Thus did I discover Ebba Grön. A week later I
found myself listening to
<a href="https://www.youtube.com/watch?v=oaq-3Ht4MiA&amp;">Beväpna Er</a> on repeat for almost
the full duration of a snabbtåget journey from Malmö to Stockholm. And I now
have a full-blown obsessive fixation on the lyrics of the first verse of
<a href="https://www.youtube.com/watch?v=unHS713TTAE">Staten och Kapitalet</a>.</p>

<blockquote>
  <p>Kapitalet höjer hyrorna och staten bostadsbidragen. Så kan man fiffla en smula
med den järnhårda lönelagen. Och till och med betala mindre i lön än priset
för mat och för hyra. För staten skjuter så gärna till om levnadsomkostnaderna
blivit allt för dyra.</p>
</blockquote>

<p>We’ve had a car a little over a year now and I love having a fav radio station
turn on immediately when it starts. Feels just like GTA. Mine’s Din Gata, which
is a hip hop station whose only FM transmitter is in Malmö. Driving out of town,
it tends to cut out after Helsinborg or Höör, at which point Rock FM or Retro FM
usually come on instead. There is a fun sense of homecoming on the way home upon
coming back within FM range of Din Gata and switching back.</p>

<figure>
 <img alt="Map of the Öresund region showing a red zone over Malmö and then concentric yellow and green zones expanding out beyond" src="/2025/11/17/dingata@1366x1094.webp" />
 <figcaption>
  <a href="https://driftstatus.teracom.se/privat/radio">Teracom coverage map for Din Gata</a>
 </figcaption>
</figure>

<p>The year’s most unforgettable music moment came unexpectedly during the first of
several trips to see A Minecraft Movie, when the entire audience sang along to
<a href="https://www.youtube.com/watch?v=41O_MydqxTU">Steve’s Lava Chicken</a>. Incredible
experience. We are a Lava Chicken household at this point and any
chicken-adjacent food can be made instantly delicious simply by declaring it to
<em>be</em> lava chicken.</p>

<p>I think Rasmus was really onto something with this post today. There’s an
element of <a href="https://www.danmcquillan.org/decomputing.html">decomputing</a> to it
almost, in that it’s an implicit critique of the
<a href="https://danmcquillan.org/decomputing_as_resistance.html">datafication</a> of
music’s role in our lives. The cultural clout of this annual surveillance
capitalism PR campaign reflects so much of what’s gone wrong with our society.
Music is an art form that permeates our whole society and our relationships with
it are so much more interesting than our telemetry data from an app we sometimes
use.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="music" />
      

      
      
        <summary type="html"><![CDATA[Rasmus Fleischer made an interesting appeal earlier today.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/11/17/aristocats@600x596.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/11/17/aristocats@600x596.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">The future of open source is democratic, community-led and non-profit</title>
      <link href="https://henry.catalinismith.se/2025/10/23/the-future-of-open-source-is-democratic-community-led-and-non-profit/" rel="alternate" type="text/html" title="The future of open source is democratic, community-led and non-profit" />
      <published>2025-10-23T00:00:00+02:00</published>
      <updated>2025-10-23T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/10/23/the-future-of-open-source-is-democratic-community-led-and-non-profit</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/10/23/the-future-of-open-source-is-democratic-community-led-and-non-profit/"><![CDATA[<p>I’m <em>so fucking bored</em> of companies.</p>

<p>Venture capital enjoyed a decade or more setting the agenda in the tech
industry. For me that whole narrative has become like that one overplayed song
that you’ll change radio station to avoid hearing again.</p>

<figure>
 <img src="/2025/10/23/radio@640x530.webp" alt="Man leaning out of bath to change radio channel" />
 <figcaption>🎶 Anxiety, keep on tryin' me…</figcaption>
</figure>

<p>In hindsight I think this business fatigue was a big part of why
<a href="/2025/03/07/codeberg-is-the-future/">I was so stoked to join Codeberg e.V.</a>
earlier this year. Little things like
<a href="/2025/07/29/voting-on-codebergs-new-privacy-policy/">getting to actually vote on the new privacy policy</a>
or
<a href="/2025/09/02/democracy-kicks-ass/">which types of license should count as free</a>
turned out to be unexpectedly intoxicating expressions of autonomy.</p>

<p>Then came
<a href="https://pup-e.com/goodbye-rubygems.pdf">Ruby Central’s attack on RubyGems</a>. For
me James Coglan summarised this best.</p>

<blockquote>
  <p>the ruby ecosystem, in particular the people running the package repo, have
set a precedent that if you make something and it becomes important, it can be
taken from you with no due process</p>

  <p><a href="https://mastodon.social/@jcoglan/115389887137608429">https://mastodon.social/@jcoglan/115389887137608429</a></p>
</blockquote>

<p>It’s all culminated in a realisation: I’m becoming less and less interested in
neat little technical innovations that incrementally improve some workflow while
reinforcing these legacy power structures where capital calls the shots. I need
a better world more than I need a better computer.</p>

<p>Two examples of this type of shallow tech-centric innovation that I see getting
some hype right now would be <a href="https://atproto.com/">Bluesky’s AT protocol</a> and
<a href="https://github.com/jj-vcs/jj">Google’s Jujutsu version control system</a>. In my
opinion both of these are examples of a growing pattern of what I’d call
“governance theatre” where capital decorates some of its sociotechnical leverage
with nice-sounding words like “open” and “decentralised” while retaining true
strategic control for itself.</p>

<p>Jujutsu provides a neat example of governance theatre in the commit message for
<a href="https://github.com/jj-vcs/jj/commit/d8feed9be451053de8942c3cd6be21bbc8a1bd33">commit d8feed9</a>.</p>

<blockquote>
  <p>copyright: change from “Google LLC” to “The Jujutsu Authors”</p>

  <p>Let’s acknowledge everyone’s contributions by replacing “Google LLC” in the
copyright header by “The Jujutsu Authors”. If I understand correctly, it won’t
have any legal effect, but maybe it still helps reduce concerns from
contributors (though I haven’t heard any concerns).</p>

  <p>Google employees can read about Google’s policy at
go/releasing/contributions#copyright.</p>
</blockquote>

<p>Beyond the self-admitted performative nature of the change and the irony of
including an URL that only works on Google’s VPN in a commit message attempting
to make the project seem less affiliated with Google, the elephant in the room
is that you have to go to https://cla.developers.google.com/clas and sign a CLA
before you can even contribute to this project.</p>

<p>I find atproto even more insidious because of the way the sales pitch for it
explicitly acknowledges how problematic these power structures have become and
tries to sell you a tech fix to a human problem. Dan Abramov’s
<a href="https://overreacted.io/open-social/">Open Social</a> post captured the spirit of
this best.</p>

<blockquote>
  <p>However, collectively, the net effect is that social platforms—at first,
gradually, and then suddenly—turn their backs on their users. If you can’t
leave without losing something important, the platform has no incentives to
respect you as a user.</p>

  <p>Maybe the app gets squeezed by investors, and every third post is an ad. Maybe
it gets bought by a congolomerate that wanted to get rid of competition, and
is now on life support. Maybe it runs out of funding, and your content goes
down in two days. Maybe the founders get acquihired—an exciting new chapter.
Maybe the app was bought by some guy, and now you’re slowly getting cooked by
the algorithm.</p>

  <p><a href="https://overreacted.io/open-social/">https://overreacted.io/open-social/</a></p>
</blockquote>

<p>The difference between Dan and I is that he buys the idea that you can patch a
problem with human power structures using a sufficiently clever technical trick,
while I don’t. I think you have to confront the power issue directly, and I
think that’s what Codeberg’s democratic community-led non-profit structure is
such a great example of.</p>

<p>So when I see these new Codeberg competitors springing up, I struggle to get as
excited as I did for Codeberg itself. I look at things like Tangled and East
River Source Control and I see startup guys betting big on pseudo-open things
like Bluesky’s AT protocol and Google’s Jujutsu version control system. All I
see is Ned Ryerson bounding over yet again to sell me life insurance.</p>

<figure>
 <img src="/2025/10/23/ryerson@618x412.webp" alt="Ned Ryerson alongside Bill Murray in Groundhog Day" />
 <figcaption>I sure as <em>heck fire</em> remember you!</figcaption>
</figure>

<p>Been down that road. Know where it ends. Bored absolutely shitless of it. What’s
most exciting to me now in the VCS space is seeing through this experiment in
democratic non-profit community-led ownership that
<a href="https://codeberg.org/">Codeberg</a> represents.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[I’m so fucking bored of companies.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/10/23/radio@640x530.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/10/23/radio@640x530.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">My smartphone was making me fat</title>
      <link href="https://henry.catalinismith.se/2025/10/21/my-smartphone-was-making-me-fat/" rel="alternate" type="text/html" title="My smartphone was making me fat" />
      <published>2025-10-21T00:00:00+02:00</published>
      <updated>2025-10-21T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/10/21/my-smartphone-was-making-me-fat</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/10/21/my-smartphone-was-making-me-fat/"><![CDATA[<p>It’s been about five months now since I
<a href="/2025/08/20/notes-from-three-months-without-carrying-a-smartphone/">stopped carrying a smartphone</a>.
Recently my BMI re-entered the “healthy” range for the first time in about five
years. I’m pretty convinced the two are connected and here’s why.</p>

<p>Before I got the Nokia my daily screen time on the phone was on the order of
several hours. All those moments sitting still looking at some app really add
up. If I have eight hours left in my day after subtracting sleep and work,
spending half of those sitting still looking at a phone doesn’t leave a lot of
time for much else.</p>

<p>Becoming a parent made me feel extremely time-poor. Ditching the phone had the
opposite effect. It added extra hours to my day.</p>

<p>Another effect of kicking the smartphone addiction was the return of boredom to
my everyday life. This was good! It was something I was specifically aiming for
too, like kind of a
<a href="https://phirephoenix.com/blog/2025-10-11/friction">choosing friction</a> sort of
thing.</p>

<p>The secondary effects were a surprise though. The boredom reawoke long dormant
impulses to be creative and physically active. Basketball courts and skateparks
became places to spend some of all that newfound free time.</p>

<p>Perhaps most important of all was how much my relationship with temptation and
willpower has improved as a result of dealing with the addiction to the phone.
The problem wasn’t just the simple logistical issue of spending a lot of time on
the thing, or that the resulting absence of boredom was ultimately harmful.
Deeper than that, I think the everyday act of repeatedly choosing to surrender
to that craving slowly grew to a more general problem around impulse control.</p>

<p>So after several months clean what I’ve found is that even the temptation to buy
e.g. a chocolate bar during a grocery store trip has started to become way more
manageable. Same thing for the occasional bright idea to have a few beers after
putting the kids to bed.</p>

<p>I definitely won’t be going back. The fucking things are just too engaging and
it’s no accident. Every app on there has a whole little army of super talented
strategists and engineers and designers behind it, all conspiring to squeeze an
ever-increasing amount of “engagement” out of you so that they can put a graph
going up and to the right in a PowerPoint slide.</p>

<p>Smartphones must seriously be the only modern addiction where people try to
recover while still carrying the object of their addiction around with them. Can
you imagine how insane it would be to try to recover from alcoholism but
continue taking a hip flask everywhere just because it happens to have a really
convenient slot to store a drivers’ license and credit card?</p>

<p>No, fuck that. I got clean and now I’m staying clean. Once a week or so I pull
up the list of apps on the old iPhone and try to find one I can delete. It’s
down to about 30-ish left. Hopefully within a year or so I’ll be finished
untangling my life from it completely. Then maybe sell it or something and take
the relapse risk down close to zero.</p>

<p>Finding an app you can delete on your phone right now might work pretty well as
a first step of your own come to think of it.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[It’s been about five months now since I stopped carrying a smartphone. Recently my BMI re-entered the “healthy” range for the first time in about five years. I’m pretty convinced the two are connected and here’s why.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Rosa Luxemburg predicted the AI crash and its consequences in 1913</title>
      <link href="https://henry.catalinismith.se/2025/10/20/rosa-luxemburg-predicted-the-ai-crash-in-1913/" rel="alternate" type="text/html" title="Rosa Luxemburg predicted the AI crash and its consequences in 1913" />
      <published>2025-10-20T00:00:00+02:00</published>
      <updated>2025-10-20T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/10/20/rosa-luxemburg-predicted-the-ai-crash-in-1913</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/10/20/rosa-luxemburg-predicted-the-ai-crash-in-1913/"><![CDATA[<p>The Accumulation of Capital is a book from 1913 by Rosa Luxemburg which predicts
the collapse of the AI bubble. Cool, right?</p>

<p>She describes how capitalist production is structured around the manufacture of
commodities, the appropriation of surplus value from their sale, the realisation
of that value, and then the endless recycling of that value in search of
infinite growth. It’s pretty heavy on words, but it’s an absolutely killer book
that’s well worth a read.</p>

<p>It’s the neverending greed for more <em>more</em> <strong><em>MORE</em></strong> where the analysis in the
book starts to sound like a prediction of the AI bubble.</p>

<blockquote>
  <p>Expansion becomes a condition of existence. A growing tendency towards
reproduction at a progressively increasing scale thus ensues, which spreads
automatically like a tidal wave over ever larger surfaces of reproduction.</p>
</blockquote>

<p>I think about that bit every time I open LinkedIn and see an AI startup revenue
graph going up and to the right.</p>

<figure>
 <img src="/2025/10/20/disco-stu@640x480.webp" alt="Disco Stu from the Simpsons next to a graph going up and to the right labeled 'Disco Record Sales', where the x-axis is 1973 to 1976, the trend is hockeystick growth, and the caption 'If these trends continue... ay!!'" />
</figure>

<p>Anyway.</p>

<p>She talks about how the surplus value is the driving force behind it all, and
explains that the goods themselves are almost incidental. That much is basically
<a href="https://en.m.wikipedia.org/wiki/The_Lean_Startup"><em>Lean Startup</em></a> translated
into marxist vocabulary. Then she drops this.</p>

<blockquote>
  <p>Conversely, capital may, within limits, yield a greater surplus value in
consequence of a higher degree of exploitation such as is brought about by
wage-cutting and the like, without actually producing a greater amount of
goods.</p>
</blockquote>

<p>Spookily prescient description of modern events IMO. In so many cases this AI
“gold rush” is about growing margins in a negative way through the cutting of
operational costs, rather than positive growth resulting from innovations that
improve anyone’s material conditions.</p>

<p>Trouble is, realising the surplus value from those bigger margins depends on
society. Specifically, it depends on the people in society having money to buy
anything. Here’s a longer quote about that. See what I mean about it being quite
wordy? Rewarding reading if you can challenge yourself to sit patiently with the
text though, I promise!</p>

<blockquote>
  <p>But it is this very licence and anarchy of the commodity market which brings
home to the individual capitalist that he is dependent upon society, upon the
entirety of its producing and consuming members. The individual capitalist may
need additional means of production, additional labour and provisions for
these workers in order to expand reproduction, but whether he can get what he
needs depends upon factors and events beyond his control, materialising, as it
were, behind his back. In order to realise his increased aggregate of
products, the individual capitalist requires a larger market for his goods,
but he has no control whatever over the actual increase of demand in general,
or of the particular demand for his special kind of good.</p>
</blockquote>

<p>So, about that need for a market for the goods! Any reduced demand for labour
resulting from AI carries an associated downward pressure on consumer purchasing
power. In other words, if the computer steals your job<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, you’re probably
gonna buy less stuff, right?</p>

<p>This reduced purchasing power in turn is a potential crisis for a mode of
production dependent on infinite growth. In other words, if enough people are
buying less stuff because the computer stole their jobs<sup id="fnref:1:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, where’s the next
round of infinite growth gonna come from?</p>

<p>I think about that part of the book whenever I see that viral “AI money machine”
graphic from Bloomberg. You know the one I mean.</p>

<figure>
 <img src="/2025/10/20/bloomberg@1296x1584.webp" alt="How Nvidia and OpenAl Fuel the Al Money Machine graphic showing circular economy with the same money moving back and forth among OpenAI, Nvidia, MSFT etc" />
 <figcaption>This one!</figcaption>
</figure>

<p>Pick out one of those companies that’s more directly exposed to changes in
consumer demand. Microsoft, I guess? Then visualise the domino effect on this
whole circular economy when slumping consumer demand fucks with their cashflow.</p>

<p>Contemporary mainstream commentary on the AI bubble takes a neoliberal
perspective focusing on the business fundamentals of these companies. It tries
to answer the question “is this a bubble?” and then justify its conclusions.
Interesting, but superficial.</p>

<p>What I love about The Accumulation of Capital is that takes a more holistic
perspective on the overall pattern of these boom and bust cycles. It gets to the
core of the problem, which is the <em>Groundhog Day</em> inevitability of it all.</p>

<blockquote>
  <p>This periodical fluctuation between the largest volume of reproduction and its
contraction to partial suspension, this cycle of slump, boom, and crisis, as
it has been called, is the most striking peculiarity of capitalist
reproduction.</p>
</blockquote>

<p>Another great thing about this book is that it gets into the chain reaction
mechanics of the crash in a way that I find really engaging.</p>

<blockquote>
  <p>Reproduction here depends on purely social considerations: only those goods
are produced which can with certainty be expected to sell, and not merely to
sell, but to sell at the customary profit. Thus profit becomes an end in
itself, the decisive factor which determines not only production but also
reproduction. Not only does it decide in each case what work is to be
undertaken, how it is to be carried out, and how the products are to be
distributed; what is more, profit decides, also, at the end of every working
period, whether the labour process is to be resumed, and, if so, to what
extent and in what direction it should be made to operate.</p>
</blockquote>

<p>Assuming token pricing levels are currently unsustainable without all the
investor cash sloshing around, it’s not super difficult to imagine what happens
if the tide goes out.</p>

<p>The most important element of The Accumulation of Capital though is the theme of
imperialism. This is where the book gets into the consequences of the crash in a
way I find contemporary commentary on AI weirdly uninterested in.</p>

<blockquote>
  <p>For capital, the standstill of accumulation means that the development of the
productive forces is arrested, and the collapse of capitalism follows
inevitably, as an objective historical necessity. This is the reason for the
contradictory behaviour of capitalism in the final stage of its historical
career: imperialism.</p>
</blockquote>

<p>She gets into this whole bit about militarism and war economy after this that I
find almost too upsetting to read. I wish this part of the book didn’t also
resonate so well with current events, where every tech billionaire you can think
of suddenly has some kind of military thing in their portfolio.</p>

<p>Cool
<a href="https://www.marxists.org/archive/luxemburg/1913/accumulation-capital/accumulation.pdf">book</a>
though!</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">

      <p>How many of the AI-related job losses so far are due to people being laid
off to free up money for speculative investment in AI vs people being
displaced by working AI software anyway? <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
  </ol>
</div>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[The Accumulation of Capital is a book from 1913 by Rosa Luxemburg which predicts the collapse of the AI bubble. Cool, right?]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/10/20/disco-stu@640x480.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/10/20/disco-stu@640x480.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">The Tinder Box is a Morrowind Let’s Play</title>
      <link href="https://henry.catalinismith.se/2025/09/14/the-tinder-box-is-a-morrowind-lets-play/" rel="alternate" type="text/html" title="The Tinder Box is a Morrowind Let’s Play" />
      <published>2025-09-14T00:00:00+02:00</published>
      <updated>2025-09-14T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/09/14/the-tinder-box-is-a-morrowind-lets-play</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/09/14/the-tinder-box-is-a-morrowind-lets-play/"><![CDATA[<p>When I was little my grandad used to read me this book called The Tinder Box. I
got hold of a copy a few years back in order to have something with some
nostalgic value in the bedtime story rotation. I’ve been reading it quite
frequently ever since.</p>

<p><img loading="lazy" src="/2025/09/14/cover@1024x837.webp" alt="   Soldier in a red andw white uniform holding a tinder box in his right hand and a helmet full of gold coins in his left.  " /></p>

<p>It is a deeply strange and unpleasant book which probably shouldn’t exist. It’s
also gradually dawned on me that it follows the narrative structure of a
Morrowind playthrough. Now I need to express this idea in order to be free of
it. So that’s what you’re about to read.</p>

<p>The story kicks off with the protagonist – a soldier – walking around in the
countryside. He stumbles on an NPC who sends him into a cave on a fetch quest.</p>

<p><img src="/2025/09/14/fetch-quest@1024x781.webp" alt="   'Soldier, would you like a lot of money?', called the witch.   'Very much' said the solder, 'What would I have to do?'   The witch pointed to a big tree.   'That tree is hollow', she said.   'I want you to climb down inside. I'll tie a rope around your body and pull you up when you call. Then you'll be rich.'  " /></p>

<p>During the course of his fetch quest the soldier repeatedly becomes
over-encumbered with loot. Entire pages are dedicated to inventory management in
order for the soldier to minmax the value of the loot he’s carrying.</p>

<p><img src="/2025/09/14/over-encumbered@1024x1489.webp" alt="   There were so many gold coins that the soldier gasped.   He threw away the silvrer.   He filled his pockets, his knapsack, and even his cap with gold!   Only then did he put the dog back on the chest.  " /></p>

<p>After leaving the cave, the soldier opts to kill the NPC who assigned the quest
instead of completing it. He has no good reason to do this apart from XP farming
and to further minmax the cash value of his inventory by holding onto the quest
item.</p>

<p><img loading="lazy" src="/2025/09/14/killed-her-with-his-sword@1024x772.webp" alt="   'Where is the tinder box?' screeched the witch.   'Give it to me!'   The soldier shook his head.   'First of all, tell me why you want it,' he said.   'I won't!' the witch screamed.   'Give it to me at once!'   'No,' said the soldier, and he killed her with his sword.  " /></p>

<p>Upon his arrival in town the soldier heads straight to the vendors to spend his
quest loot on gear. The remainder is spent maxing out his disposition with local
NPCs.</p>

<p><img loading="lazy" src="/2025/09/14/town@1024x797.webp" alt="   The soldier set off for the town.   It was a splendid place, and now he was rich.   He took a room at the very best inn.   He ordered the best food, and bought new clothes.   Soon he became a fine gentleman, and had lots of friends.  " /></p>

<p>The very first time he runs out of gold the soldier figures out an infinite
money glitch involving a talking magical creature and begins to exploit it.</p>

<p><img loading="lazy" src="/2025/09/14/infinite-money@1024x779.webp" alt="   He brought out the tinder box.   As soon as he struck a spark from the flint, the door of his room flew open.   In came the first dog he'd met, down inside the hollow tree.   'What are your commands, lord?' the dog asked.   'Bring me some money!' cried the solder.   In a flash the dog vanished.   He returned with a bag of copper coins in his mouth.  " /></p>

<p>Eventually he gets carried away using his summoned dogs to mess around with NPCs
in the town and gets caught by the guards and jailed. He glitches his
game-breaking magical artefact back into his inventory, escapes, and then
summons his dogs to kill all the guards and stage a coup d’état,</p>

<p><img loading="lazy" src="/2025/09/14/end@1024x728.webp" alt="   The dogs were so fierce that the king's men bolted.   The dogs seized the king and queen and ran away with them.   They were never seen again.   The people cheered the soldier.   'Marry our princess', they cried.   'Then you shall be our king and queen!'   And so the soldier married the princess, which pleased her very much.   Their wedding lasted a week, and the three dogs were the most important guests at the feast.  " /></p>

<p>The book ends abruptly here with the soldier having been richly rewarded for his
ruthlessness and greed. If there’s a moral to this story, I’d say it’s “The
strong do what they can and the weak suffer what they must”. In a way it was the
perfect bedtime story for an 80’s baby.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[When I was little my grandad used to read me this book called The Tinder Box. I got hold of a copy a few years back in order to have something with some nostalgic value in the bedtime story rotation. I’ve been reading it quite frequently ever since.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/09/14/cover@1024x837.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/09/14/cover@1024x837.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Glory Days Or Bust</title>
      <link href="https://henry.catalinismith.se/2025/09/03/glory-days-or-bust/" rel="alternate" type="text/html" title="Glory Days Or Bust" />
      <published>2025-09-03T00:00:00+02:00</published>
      <updated>2025-09-03T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/09/03/glory-days-or-bust</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/09/03/glory-days-or-bust/"><![CDATA[<p>18 months ago I was all-in on VSCode. lol</p>

<blockquote>
  <p>Once upon a time, the main tools I used for work were Vim and tmux. And until
recently, once every few months I’d get this compulsive urge to try to
recapture those glory days. That urge has stopped lately though. Visual Studio
Code has reached a tipping point where the Vim support and terminal emulator
are a better package than anything I can hack together with lua plugins and
tmux config.</p>

  <p><a href="/2024/01/30/maining-vscode-terminal/">Maining VSCode Terminal</a></p>
</blockquote>

<p>Hasn’t aged well! Have you seen the VSCode website lately?</p>

<p><img src="/2025/09/03/vscode@512x256.webp" alt="The open source AI code editor" /></p>

<p>Yikes!</p>

<figure>
 <video controls="" preload="none" poster="/2025/09/03/glwat@512x384.webp">
  <source src="/2025/09/03/glwat@512x384.mp4" type="video/mp4" />
 </video>
</figure>

<p>So now I’m all-out. It’s glory days or bust!</p>

<p>Real happy with
<a href="https://codeberg.org/henrycatalinismith/dotfiles/src/branch/main/dot_config/nvim/init.lua">my init.lua</a>
for Neovim. Doing Vim config in Lua is such a dream come true. For everyday
work, my core needs are simple enough.</p>

<ol>
  <li>LSP diagnostics (to highlight my mistakes)</li>
  <li>Git integration (to highlight my changes)</li>
  <li>Format on save</li>
</ol>

<p>Those use cases are so mainstream that you’re spoiled for choice regarding how
to set them up. I landed on <a href="https://github.com/nvimdev/lspsaga.nvim">lspsaga</a>,
<a href="https://github.com/lewis6991/gitsigns.nvim">gitsigns</a> and
<a href="https://github.com/stevearc/conform.nvim">conform</a>.</p>

<figure>
 <img alt="Screenshot of neovim editing this blog post. The text displayed is the same as the text preceding this image." src="/2025/09/03/screenshot@1024x635.webp" />
</figure>

<p>It’s such an incredible relief to be back working in proper Vim instead of some
IDE’s bootleg reimplementation. You really learn to appreciate how finely
crafted Vim’s core text editing functionality is by spending a few years seeing
talented people trying and failing to reproduce it in competing software time
and time again. Zed’s implementation was decent, but even that had quirks I
never quite got used to.</p>

<p><a href="https://zellij.dev/">Zellij</a> is fantastic too. It’s like a fully configured
tmux right out of the box. And while keybinding conflicts between Vim and tmux
used to be a big pain in the ass, with Zellij I’ve managed to move all its
keybindings onto the ⌘ key where there’s no conflicts at all. Its vanilla
out-of-the box UX is what I’ve been trying (and failing) to coerce VSCode or Zed
to be all these years too.</p>

<p>Wasn’t such a difficult switch to make in the end. This community-built tooling
has become <em>really</em> fucking slick over the last few years. And with all the
commercial tooling focusing so intensely on AI it seems reasonable to expect
that balance to continue to shift in the coming years. I mean if you’re a
product manager on VSCode and all your KPIs are about AI, why even prioritise
fixing code editing bugs if a degraded coding experience could arguably drive AI
adoption?</p>

<p>Nice being rid of tools that are funded with blood money and all. Did you know
<a href="https://www.aljazeera.com/news/2025/8/29/microsoft-fires-four-workers-over-protests-against-firms-ties-to-israel">Microsoft recently fired</a>
a group of <a href="https://noazureforapartheid.com/">No Azure For Apartheid</a>
protestors? Or that Zed recently took money from
<a href="https://shaunmaguire.fyi/">Shaun Maguire &amp; co</a>? Increasingly I find myself
wanting as few points of contact with this type of depravity as humanly possible
in my everyday life.</p>

<p>I had actually underestimated how good this integrity boost would feel. Seems I
had become quite numb to the dissonance of sharing in the mundane everyday
spoils of a genocidal war I was otherwise willing to
<a href="/2024/05/12/eurovision-week/">boycott a beloved event for</a>. Cool as hell to
discover that the high road is increasingly the best one too these days, at
least when it comes to devtools.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[The road back to my coding happy place, i.e. a terminal emulator running Vim.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/09/03/vscode@512x256.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/09/03/vscode@512x256.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Democracy Kicks Ass</title>
      <link href="https://henry.catalinismith.se/2025/09/02/democracy-kicks-ass/" rel="alternate" type="text/html" title="Democracy Kicks Ass" />
      <published>2025-09-02T00:00:00+02:00</published>
      <updated>2025-09-02T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/09/02/democracy-kicks-ass</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/09/02/democracy-kicks-ass/"><![CDATA[<p>Everyone was up in arms in August about
<a href="https://www.theverge.com/news/757461/microsoft-github-thomas-dohmke-resignation-coreai-team-transition">GitHub’s new status as a department of Microsoft</a>
instead of a subsidiary of Microsoft. Understandable, I guess. Moving it into
their CoreAI department makes their vision for the world’s leading open source
collaboration platform very clear, and it’s at odds with what a lot of that
community actually wants.</p>

<p>For me the uproar about it fell a little bit flat though. Since July I’ve been
getting about an email per week inviting me to vote on a new community decision
at Codeberg, and all this newfound agency has made that kind of impotent anger
harder to identify with. So instead of feeling swept up by the collective dismay
about the direction Microsoft is steering GitHub, all I could think about was
the raw potential in it.</p>

<p>Here’s an example. For a while now I’ve been following a debate in the Codeberg
community about licensing. Codeberg only provides free hosting for projects with
free licenses, and there’s a lack of clarity about exactly which licenses count
as “free enough”. Opinions vary about
<a href="https://codeberg.org/Codeberg/org/pulls/1219">how broad the criteria should be</a>
and the discussions have been interesting to read along with.</p>

<p>I’d been reading all this with the same old-fashioned mentality of powerlessness
that you get used to as a user of an undemocratic platform operated on a
for-profit basis under the dictatorship of a company. Then last week I suddenly
got one of these poll notification emails inviting me to cast my vote on the
decision and I was slightly taken aback by it. This had been the whole point of
<a href="https://join.codeberg.org/">joining the Codeberg e.V.</a> but somehow each time
that decision pays off it’s a surprise all over again.</p>

<p>So it seems like it’s taking a little while to acclimatise and raise my
expectations up to a new level of agency and dignity. But at the same time,
seeing all that despair about Microsoft’s utterly predictable consolidation of
control over GitHub made me understand how much of a mindset shift I’ve already
undergone.</p>

<p>Joining the Codeberg e.V. is becoming one of my favourite little decisions
of 2025. With the profit-driven side of the industry disappearing up its own
arsehole to deliver an AI revolution few of us seem to actually want, I think
it’s a unique opportunity for a community-led non-profit like Codeberg to gain
ground. And it’s really exciting to imagine the potential second-order effects
on the industry in general if more of us get a taste of what this kind of
democratic model can deliver.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="codeberg" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[Contrasting the sense of helplessness about Microsoft's agenda for GitHub with the community-led democracy of Codeberg.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Notes from three months without carrying a smartphone</title>
      <link href="https://henry.catalinismith.se/2025/08/20/notes-from-three-months-without-carrying-a-smartphone/" rel="alternate" type="text/html" title="Notes from three months without carrying a smartphone" />
      <published>2025-08-20T00:00:00+02:00</published>
      <updated>2025-08-20T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/08/20/notes-from-three-months-without-carrying-a-smartphone</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/08/20/notes-from-three-months-without-carrying-a-smartphone/"><![CDATA[<p>Got a Nokia 2660 for my birthday a few months back. Looks like this.</p>

<p><img src="/2025/08/20/nokia@1024x768.webp" alt="Black flip phone on a sunlit wooden table" /></p>

<p>It can do calls and texts. It has two SIM slots and a
<a href="/2025/08/01/life-after-streaming/">built-in FM radio</a>. No apps, and the web
browser will only be useful if I witness my best friend being executed by
terrorists in a mall parking lot and in my panic accidentally travel back in
time 30 years to a time before JavaScript.</p>

<p>Wanted to stop having a smartphone on me all the time. This thing was the way to
make that happen. The smartphone is still around, but it stays at home on
standby like a sort of shit little computer. Seemed a bad idea to try to get rid
of it completely on the same day as getting the Nokia, which proved wise. Given
that the average person spends about four hours per day on their phone, it’s a
significant lifestyle change. It’s a bit of a process.</p>

<p>After reaching the three month mark this week I checked my data usage and
realised I’ve used exactly 0GB and I’m now wasting a fortune on a service I
don’t need any more. So I’ve switched over to a cheap pay-as-you-go SIM, and
this felt like a good milestone to write down some notes about the
practicalities of the whole thing.</p>

<p>Car-related stuff is a good example of how smartphone-centric life has become. A
lot of car parks don’t have a ticket machine any more. I wasn’t sure how I would
even handle this. Parkster has proved to be the secret ingredient with their SMS
parking feature. Once your account is set up you don’t need a smartphone to text
their system the car park’s ID number and your car registration number. Works
great. The lack of push notifications reminding you about your car means you do
have to remember you own a car the old fashioned way, by relying on
<a href="https://en.m.wikipedia.org/wiki/Object_permanence">object permanence</a>. After
three months of field experience I am ready to say confidently that this skill
can be re-learned.</p>

<p>Contact with schools is another very app-driven part of modern life. Fortunately
the school apps I’m forced to use – Tempus Hemma, Skola24 and Unikum – all have
websites that provide the same functionality the apps do. Obviously you lose out
on the push notifications this way, but that’s kind of the point. After some
reflection I decided I can cope without the phone buzzing in my pocket after the
preschool drop-off to let me know that I’ve just done the preschool drop-off. I
already know. I was there! Even easier to cope without is the pointless panic
that used to ensue whenever the staff at preschool accidentally pressed the
wrong name during checkout for someone else’s kid, sending me a weird
false-positive <a href="https://en.m.wikipedia.org/wiki/Amber_alert">amber alert</a> for my
own kid.</p>

<p>All the banks I have accounts with have web apps too. You can’t use them on the
1950s web browser on my Nokia but I struggle on. Sometimes I vaguely miss the
at-a-glance convenience of the “look how surprisingly little money is left”
anxiety widget on my smartphone homescreen. Fortunately I’ve found that if I
really get nostalgic for that feeling I can emulate it by downing a really
strong cup of coffee and thinking about death. And thanks to the same object
permanence trick that helps me remember my car, I’m also managing to remember
I’m not a millionaire, despite only checking my finances once every day or two.</p>

<p>BankID is fine and all. The USB one for the computer works great. Admittedly,
not having <em>Mobile</em> BankID does get in the way of running around in a state of
constant distraction, staring down at my phone to file my taxes while I cross a
busy road, or authorising a payment for cat litter while I barely notice the
food I’m eating during lunch. Three months in, the cat litter purchases have
managed to continue keeping pace with cat litter usage despite this obstacle. I
suppose we will have to wait until the 2026 tax declaration season to find out
if it works to file in the safety of my home instead of as a death-defying act
of pedestrian multitasking. I’m feeling lucky though.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[Got a Nokia 2660 for my birthday a few months back. Looks like this.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/08/20/nokia@1024x768.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/08/20/nokia@1024x768.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Nine Nine Six</title>
      <link href="https://henry.catalinismith.se/2025/08/11/nine-nine-six/" rel="alternate" type="text/html" title="Nine Nine Six" />
      <published>2025-08-11T00:00:00+02:00</published>
      <updated>2025-08-11T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/08/11/nine-nine-six</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/08/11/nine-nine-six/"><![CDATA[<video controls="" preload="none" poster="/2025/08/11/996-cover@1024x576.webp">
 <source src="/2025/08/11/996@1024x576.mp4" type="video/mp4" />
</video>

<p>I keep hearing about these
<a href="https://www.youtube.com/shorts/MpeMWN4EofE">AI startups where people are doing 72 hour weeks</a>.
It reminds me of
<a href="https://www.tiktok.com/@andrewrousso/video/7206065467918748971?lang=en">a parody video</a>
I saw a few years back so I made this little split-screen edit.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/08/11/996-cover@1024x576.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/08/11/996-cover@1024x576.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Life after streaming</title>
      <link href="https://henry.catalinismith.se/2025/08/01/life-after-streaming/" rel="alternate" type="text/html" title="Life after streaming" />
      <published>2025-08-01T00:00:00+02:00</published>
      <updated>2025-08-01T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/08/01/life-after-streaming</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/08/01/life-after-streaming/"><![CDATA[<p>Been a lot of buzz about getting off Spotify due to the military industrial
profiteering. It’s fair enough, but I’d like to encourage people to treat it as
an opportunity to upgrade their relationship with music rather than just
installing a competing app and trying to carry on as before. I uninstalled that
app myself a while ago for <em>other reasons</em> and tried a bunch of the competitors
for a while before coming to this conclusion. Maybe I can save you wasting a
bunch of time pissing around with importing and exporting playlists over and
over.</p>

<p>All these services share more or less the same music library, the same key
suppliers, and the same economic incentives and pressures. Take it from someone
who’s sat in two different meeting rooms named “Rage Against The Machine” at two
different music streaming companies. It’s “tech guy who thinks he’s the good
guys because he rides a skateboard to the office” all the way down, and that’s
coming from someone who rode a skateboard to both those jobs.</p>

<p>You can do a ton of analysis and research to pick your replacement app and
you’re gonna get 99% the same thing no matter which one you choose. It’ll have
100 million-ish tracks and it’ll do a bunch of collaborative filtering to try to
recommend new tracks to you from that dataset. A corporate gameshow of KPIs and
performance reviews will drive a small army of talented people to work day and
night to modify your behaviour to produce statistics to impress investors. Music
will continue to be part of “going on the computer” for you instead of its own
thing.</p>

<p>A little FM radio that doesn’t even need batteries might cost you between one
and two months of streaming subscription money. Personally, I was amazed by how
much meaning I found in the process of learning what was broadcasting locally
and when. Connecting with the actual place where I physically live made me
realise how sterile and isolating the “going on the computer” version of music
discovery had become for me. Like it was fun to learn that some of my fav
stations broadcast from the roofs of
<a href="https://en.m.wikipedia.org/wiki/Turning_Torso">Turning Torso</a> and
<a href="https://en.m.wikipedia.org/wiki/Kronprinsen">Kronprinsen</a>, both of which are
visible from our front window.</p>

<p>A vinyl record from an actual shop will usually cost between one and four months
of streaming subscription money. The great thing about those shops compared to
the apps is that the shop is the same for everyone. They can’t personalise the
record shop based on their surveillance camera footage in order to make you stay
longer, or
<a href="https://harpers.org/archive/2025/01/the-ghosts-in-the-machine-liz-pelly-spotify-musicians/">trick you into listening to cheap knockoff music they’ve made themselves</a>
to improve their unit economics.</p>

<p>Dan MacQuillan has been talking about the concept of
“<a href="https://www.danmcquillan.org/decomputing.html">decomputing</a>” a lot lately in
the context of resisting AI. It’s about rejecting the datafication of
everything, the alienating ideology of efficiency, and instead emphasising human
judgement and local context. He talks about how the problems AI is causing in
society aren’t new or revolutionary, but rather incremental steps as part of a
longer process that’s been ongoing for a while. It’s a critique that would apply
very well to Helsing’s AI drones, now that I think about it.</p>

<p>I think it applies really neatly to the conversations I’m seeing about music
too. For example, there’s a lot of
<a href="https://www.reddit.com/r/musicindustry/comments/1i22hsd/spotify_flooded_with_ai_generated_music/">debate about these AI-generated slop songs</a>
and whether these streaming platforms should allow them. From a decomputing
perspective the real turning point happened several moves earlier in the chess
game, when decision making authority over what music to play was delegated to
the computer. Nobody would care about spammers uploading millions of AI slop
songs without algorithmic music discovery giving all that spam a shot at going
viral. In short, if the computer isn’t giving you the outcomes you want any
more, how about turning it off and trying something new?</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="music" />
      

      
      
        <summary type="html"><![CDATA[Been a lot of buzz about getting off Spotify due to the military industrial profiteering. It’s fair enough, but I’d like to encourage people to treat it as an opportunity to upgrade their relationship with music rather than just installing a competing app and trying to carry on as before. I uninstalled that app myself a while ago for other reasons and tried a bunch of the competitors for a while before coming to this conclusion. Maybe I can save you wasting a bunch of time pissing around with importing and exporting playlists over and over.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Voting on Codeberg’s new privacy policy</title>
      <link href="https://henry.catalinismith.se/2025/07/29/voting-on-codebergs-new-privacy-policy/" rel="alternate" type="text/html" title="Voting on Codeberg’s new privacy policy" />
      <published>2025-07-29T00:00:00+02:00</published>
      <updated>2025-07-29T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/07/29/voting-on-codebergs-new-privacy-policy</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/07/29/voting-on-codebergs-new-privacy-policy/"><![CDATA[<p>Nobody cares about privacy policies. Except when they do.</p>

<p><img src="/2025/07/29/fb-hoax@619x348.webp" alt="Deadline tomorrow !!! Everything you've ever posted becomes public from tomorrow. Even messages that have been deleted or the photos not allowed It costs nothing for a simple copy and paste, better safe than sorry. Channel
13 News talked about the change in Facebook's privacy policy. I do not give Facebook or any entities associated with Facebook permission to use my pictures, information, messages or posts, both past and future. With this statement, I give notice to Facebook it is strictly forbidden to disclose, copy, distribute, or take any other action against me based on this profile and/or it: contents. The content of this profile is private and confidential information.
The violation of privacy can be punished by law (UCC 1-308- 1 1 308-103 and the Rome Statute). NOTE: Facebook is now a public entity." /></p>

<p>Once in a while some big tech platform updates their privacy policy to award
themselves even more access to our personal data.
<a href="https://en.m.wikipedia.org/wiki/Reception_and_criticism_of_WhatsApp_security_and_privacy_features#2021">WhatsApp’s 2021 privacy policy update</a>
springs to mind, as well as any number of scandals about privacy on platforms
like Facebook or TikTok.</p>

<p>Every time it happens, communities scramble to understand the changes after the
fact and pressure the company to walk back some aspect of the changes.
Sometimes, like with the “Deadline tomorrow!!!” copypasta screenshot above,
there wasn’t even a policy change to begin with, and peoples’ latent sense of
distrust and powerlessness just led them to hedge their bets and copypaste the
magic words onto their wall.</p>

<p>In that context I thought it was actually pretty cool receiving this email from
Codeberg this week inviting members to vote on their new privacy policy.</p>

<blockquote>
  <p>After a lot of work and long discussions, the draft for the new privacy policy
is in its final form and ready for voting. An update to our privacy policy is
absolutely required legally due to the DSGVO since quite a long time ago - the
first draft was presented at the last annual assembly, and now we are finally
at a state that’s legally checked and reviewed in-depth by some of you.</p>

  <p>As a member of Codeberg e.V. do you support the following resolution:</p>

  <p>”The members of Codeberg e. V. hereby agree to update the privacy policy
according to https://codeberg.org/Codeberg/org/pulls/54”</p>
</blockquote>

<p>I wrote <a href="/2025/03/07/codeberg-is-the-future/">about joining Codeberg</a> earlier
this year. This type of vote is the payoff for pooling your resources in
democratic, community-led infrastructure. You get a meaningful say in how it
evolves instead of just playing catch-up after corporate headquarters drops
their latest announcement.</p>

<p>More than ever I’m convinced this is the future we should choose for the free
software and open source world. And it’s easy to join the revolution yourself
over at <a href="https://join.codeberg.org/">join.codeberg.org</a> once you’ve
<a href="https://codeberg.org/user/sign_up">created your account</a>.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="codeberg" />
      

      
      
        <summary type="html"><![CDATA[Nobody cares about privacy policies. Except when they do.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/07/29/fb-hoax@619x348.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/07/29/fb-hoax@619x348.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Is metadata streaming really SEO armageddon?</title>
      <link href="https://henry.catalinismith.se/2025/06/15/is-metadata-streaming-really-seo-armageddon/" rel="alternate" type="text/html" title="Is metadata streaming really SEO armageddon?" />
      <published>2025-06-15T00:00:00+02:00</published>
      <updated>2025-06-15T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/06/15/is-metadata-streaming-really-seo-armageddon</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/06/15/is-metadata-streaming-really-seo-armageddon/"><![CDATA[<p>This blog post called
<a href="https://omarabid.com/nextjs-vercel">Next.js 15.1+ is unusable outside of Vercel</a>
has been doing the rounds this past week.</p>

<blockquote>
  <p>Starting with version 15.1.8, Next.js might break metadata handling for
non-Vercel deployments, potentially devastating your search rankings.</p>
</blockquote>

<p>It’s about
<a href="https://nextjs.org/docs/app/api-reference/functions/generate-metadata#streaming-metadata">metadata streaming</a>,
which is this new TTFB optimisation where Next can load in meta tags
asynchronously after the initial page load. The post presents this feature as an
SEO doomsday scenario. It has a tinge of
“<a href="https://dtrap-blog.acm.org/2019/09/04/hackers-can-turn-your-home-computer-into-a-bomb/">Hackers Can Turn Your Home Computer into a BOMB</a>”
energy.</p>

<p>I’m not convinced. We’ve had this version of Next running in production at work
for months. We have asynchronous
<a href="https://nextjs.org/docs/app/api-reference/functions/generate-metadata"><code class="language-plaintext highlighter-rouge">generateMetadata</code></a>
functions all over the place. Not running on Vercel. No SEO doomsday has
materialised as of yet.</p>

<p>I spent a good while rereading the article and the linked GitHub issues and
couldn’t find anything suggesting that metadata streaming behaves differently
depending on whether you host your app on Vercel or elsewhere either. The claim
that this new feature represents an escalation to a new level of vendor lock-in
seems to be based on vibes as far as I can tell.</p>

<p>There was also this part about static sites.</p>

<blockquote>
  <p>Here’s where it gets absurd. Even with static builds, metadata tags aren’t
included in the HTML head anymore. They’re bundled with React Server
Components and require JavaScript execution. Your static site server now needs
crawler detection logic just to serve basic HTML metadata.</p>
</blockquote>

<p>This claim seemed both unlikely and easy to check. So I created a
<a href="https://codeberg.org/henrycatalinismith/nextjs-static-metadata-demo/">quick test app</a>
by running <code class="language-plaintext highlighter-rouge">create-next-app</code> and then
<a href="https://codeberg.org/henrycatalinismith/nextjs-static-metadata-demo/commit/e42c24842c6ca2c0acb72e7378d6a423f9d6b927">configuring it to be a static build</a>
and
<a href="https://codeberg.org/henrycatalinismith/nextjs-static-metadata-demo/commit/6a9988d584a364a52a901472c587b393af0ad65f">adding an asynchronous metadata fetch</a>.
You can load
<a href="https://henrycatalinismith.codeberg.page/nextjs-static-metadata-demo/">the resulting webpage</a>
yourself and see the result in its source code.</p>

<p><img src="/2025/06/15/source@1369x252.webp" alt="&lt;title&gt;Example Title&lt;/title&gt;&lt;meta name=&quot;description&quot; content=&quot;Example Description&quot;/&gt;" /></p>

<p>Metadata tags are indeed still included in the HTML head in static Next sites.
The claim seems to simply be false.</p>

<p>There’s plenty to dislike about React and Next. Even though I’ve been paying my
bills by writing JSX for the better part of a decade, Heydon Pickering’s
<a href="https://briefs.video/videos/what-is-react/">What Is React?</a> sums up my feelings
about the whole thing fairly well.</p>

<iframe width="560" height="315" src="https://www.youtube.com/embed/BO71b3l22mM?si=JXdoVlTUwVmOGUbQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>

<p>This is the dominant web development tech stack of our time and it’s flawed in
lots of really fundamental ways. There’s so much that’s problematic about it
that I don’t think we need to stretch quite this hard in search of new
complaints. Metadata streaming itself seems to embody everything that’s become
so silly about contemporary React, always growing ever more complex in order to
solve increasingly niche problems. But it does at least seem to work.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[I don't like some of these silly Rube Goldberg additions to Next.js either but I do think this one at least works as described.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/06/15/source@1369x252.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/06/15/source@1369x252.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Ubuntu 25.04 on a 2014 Macbook Air</title>
      <link href="https://henry.catalinismith.se/2025/06/10/ubuntu-2504-on-a-2014-macbook-air/" rel="alternate" type="text/html" title="Ubuntu 25.04 on a 2014 Macbook Air" />
      <published>2025-06-10T00:00:00+02:00</published>
      <updated>2025-06-10T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/06/10/ubuntu-2504-on-a-2014-macbook-air</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/06/10/ubuntu-2504-on-a-2014-macbook-air/"><![CDATA[<p>The old MacBook Air I’d been
<a href="/2024/10/01/kubernetes-homelab-on-an-old-macbook-air/">running k8s on</a> had
become a bit problematic. The battery’s gone very bad, and newer versions of
macOS have begun to run very poorly on it. After macOS Tahoe, Apple’s
<a href="https://arstechnica.com/gadgets/2025/06/macos-tahoe-signals-that-the-end-is-near-for-intel-macs-dumping-all-but-four-models/">ending support for these old Intel Macs completely</a>
too. Time to make a change!</p>

<p>It makes sense to deal with the battery later once we’ve shown we can save the
rest of the hardware, so step one was to install Ubuntu. Harder than expected!</p>

<p>First thing I did was to download <code class="language-plaintext highlighter-rouge">ubuntu-25.04-desktop-amd64.iso</code>. This boots
to GRUB, where you get these four options.</p>

<ol>
  <li>Try or Install Ubuntu</li>
  <li>Ubuntu (safe graphics)</li>
  <li>Boot from next volume</li>
  <li>UEFI Firmware Settings</li>
</ol>

<p>Couldn’t get further than this, though. Options 1 and 2 both just led to a black
screen of death. Through Googling I found various forum posts suggesting
different boot flags to try. None of that helped either.</p>

<p>I’ve been away from desktop Linux for about 15 years and I’m very out of date
with the modern troubleshooting techniques. One new thing I learned this time is
that YouTube videos have become a key source of information for this kind of
thing. In a video by someone called “Level 2 Jeff” titled
<a href="https://www.youtube.com/watch?v=1G0v5s1nEZk">Resurrecting my ancient MacBook Air with Ubuntu Linux</a>
I saw the exact same problem resolved by booting from an older Ubuntu ISO.</p>

<p>Starting again with <code class="language-plaintext highlighter-rouge">ubuntu-20.04.6-desktop-amd64.iso</code> and choosing
<code class="language-plaintext highlighter-rouge">Ubuntu (safe graphics)</code> proved successful. From there, it was a matter of
following the
<a href="https://ubuntu.com/tutorials/upgrading-ubuntu-desktop">upgrade instructions</a> to
upgrade to the latest version.</p>

<p>So far so good! And Linux looks a lot better than I remember. With any luck this
could be the first step in a journey out of a dependence on proprietary American
tech!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[The old MacBook Air I’d been running k8s on had become a bit problematic. The battery’s gone very bad, and newer versions of macOS have begun to run very poorly on it. After macOS Tahoe, Apple’s ending support for these old Intel Macs completely too. Time to make a change!]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">A GDPR bundled consent success story</title>
      <link href="https://henry.catalinismith.se/2025/05/27/a-gdpr-bundled-consent-success-story/" rel="alternate" type="text/html" title="A GDPR bundled consent success story" />
      <published>2025-05-27T00:00:00+02:00</published>
      <updated>2025-05-27T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/05/27/a-gdpr-bundled-consent-success-story</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/05/27/a-gdpr-bundled-consent-success-story/"><![CDATA[<p>A few weeks ago the Swedish government announced a new AI initiative called the
<a href="https://scandinavianmind.com/swedish-ai-reform-launches-to-give-2-3-million-swedes-free-access-to-advanced-ai-tools/">Swedish AI Reform</a>.
It’s about providing free access to AI tools to Sweden’s non-profit and public
sector workers. A decent idea, I thought, and my curiosity led me to click
through and scope out the onboarding flow.</p>

<p>Having done a lot of GDPR-related work over the years, my attention was
naturally drawn to the checkbox about consent for marketing communications. I
intentionally left it blank and submitted the form, just to see what it’d do.</p>

<figure>
  <img src="/2025/05/27/bundled-consent@864x312.webp" alt="
      Checkbox input screenshot.
      The label says 'I agree to Sana sending me marketing communications, as described in the Privacy and Cookie policy'
      Underneath is a validation error saying 'Please complete this required field'
    " />
</figure>

<p>The red validation error means the form submission failed because I hadn’t
checked the input field to indicate my consent to receive marketing
communications. In effect this makes it impossible to sign up for this service
without consenting to receive marketing communications. In GDPR nerd parlance
this is known as “bundled consent”. It’s not really allowed, but it’s an honest
mistake that’s easily made if you’re operating in a bunch of different
jurisdictions and misconfigure something by accident.</p>

<p>It took about ten minutes to
<a href="https://www.imy.se/en/individuals/forms-and-e-services/file-a-gdpr-complaint/">file a GPDR complaint with the Swedish Authority for Privacy Protection</a>.
From there it took them a couple of weeks to open and process the complaint.
They opted to forward the information to the company in question to give them
the opportunity to review their GDPR compliance themselves. And just like that,
the offending marketing consent checkbox is gone from their signup flow.</p>

<p>Very pleased with this. Nice pragmatic soft-touch approach to enforcement by the
Swedish data protection authority matched by the quick, honest fix by the data
processor. Great work by all involved and a lovely little success story to show
that this legislation is working as intended. Gotta
<a href="https://www.youtube.com/watch?v=Wtnk6DHH3JU">love living in the EU</a>.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[A few weeks ago the Swedish government announced a new AI initiative called the Swedish AI Reform. It’s about providing free access to AI tools to Sweden’s non-profit and public sector workers. A decent idea, I thought, and my curiosity led me to click through and scope out the onboarding flow.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/05/27/bundled-consent@864x312.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/05/27/bundled-consent@864x312.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">The Swan</title>
      <link href="https://henry.catalinismith.se/2025/05/26/the-swan/" rel="alternate" type="text/html" title="The Swan" />
      <published>2025-05-26T00:00:00+02:00</published>
      <updated>2025-05-26T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/05/26/the-swan</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/05/26/the-swan/"><![CDATA[<p>Every summer in Malmö a family of swans nests on the canal. Watching the parent
swans chase boats full of drunk tourists is a bit of a local pastime. Recently
went for a pedal boat ride on that same canal and was handed this wonderful bit
of accidental poetry by the operators of the pedal boat hire company.</p>

<figure>
  <img src="/2025/05/26/the-swan@1024x768.webp" alt="
      The swan's nest is marked with buoys.
      Travel on the other side of the canal.
      Don't make loud noises.
      If the swar chases the boat:
      Ignore it.
      Let it chase the boat.
      Don't shout at it.
      Don't wave stuff at it.
      Don't extend your hand.
      If you have a dog:
      Hold the dog so it doens't jump into the water.
      Swans can behave aggressively but are not dangerous.
    " />
</figure>

<p><a href="https://www.youtube.com/watch?v=lHlOXOb3vwQ">Leaving them all getting on together there</a>.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[Every summer in Malmö a family of swans nests on the canal. Watching the parent swans chase boats full of drunk tourists is a bit of a local pastime. Recently went for a pedal boat ride on that same canal and was handed this wonderful bit of accidental poetry by the operators of the pedal boat hire company.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2025/05/26/the-swan@1024x768.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2025/05/26/the-swan@1024x768.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">IAAP EU Interview</title>
      <link href="https://henry.catalinismith.se/2025/04/29/iaap-eu-interview/" rel="alternate" type="text/html" title="IAAP EU Interview" />
      <published>2025-04-29T00:00:00+02:00</published>
      <updated>2025-04-29T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2025/04/29/iaap-eu-interview</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/04/29/iaap-eu-interview/"><![CDATA[<blockquote>
  <p>This is an archive copy of an interview posted at
<a href="https://www.linkedin.com/pulse/interview-henry-catalini-smith-iaap-eu-upelf">https://www.linkedin.com/pulse/interview-henry-catalini-smith-iaap-eu-upelf</a>.</p>
</blockquote>

<h2 id="why-did-you-become-certified">Why did you become certified?</h2>

<p>We did a six month long retrofitting project in my department at Spotify, which
was a dream come true for me after many years of wanting to deepen my
accessibility knowledge. The amount of love and hard work I put into that work
led to expertise that felt worthwhile to upgrade to something more formal.</p>

<p>By the end of that retrofitting project, I was absolutely hooked too. I knew I
wanted more work like this in future. Certification seemed like a means to that
end.</p>

<h2 id="what-has-the-certification-meant-to-your-careerto-you-personally">What has the certification meant to your career/to you personally?</h2>

<p>Certification really does help attract accessibility work. Plenty of engineers
have the word WCAG on their CV because they’ve done a bit of retrofitting after
an audit. The certification is a way to stand out from that crowd as someone
who’s invested extra effort into the specialisation. It was a big factor in
landing my current job at Sveriges Radio, whose broadcast permit from the
government includes a clause imposing high accessibility standards on them. In
that sense certification has opened a door into an environment with even better
preconditions for success.</p>

<p>Once you’re inside an organisation you’re always going to start identifying
opportunities to advance their accessibility too. That might be process
improvements, technical changes, or who knows what. Then I think it’s a big
early credibility boost if you’re coming in with the same certification as the
consultants they’re already paying for audits.</p>

<h2 id="what-would-you-say-professional-certification-means-for-societyour-community-at-large">What would you say professional certification means for society/our community at large?</h2>

<p>For me its importance is in what it does for the status of accessibility in
society. Within the software industry, for example, a prejudice exists that
devalues human-oriented work on user interface software (also known as front
end) in comparison to computer-oriented work on server software (also known as
back end). The existence of certification serves as a counterweight to that,
helping to spread the understanding that accessibility is a serious intellectual
pursuit.</p>

<p>When I consider all the barriers that have been brought down by the hard-won
victories of the web accessibility community so far, I’m convinced this is one
of the most important success stories of the ongoing digital transformation of
society.</p>

<h2 id="any-additional-thoughts-on-iaap-membership">Any additional thoughts on IAAP membership?</h2>

<p>IAAP membership seemed a little abstract back in 2023 when I did my exams. Now
with my renewal dates getting closer the value seems much clearer. In hindsight
membership would’ve been a good investment from the very start.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="accessibility" />
      

      
      
        <summary type="html"><![CDATA[Archive copy of an interview with IAAP EU]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Immigrants in Tech Oppose Tighter Citizenship Rules</title>
      <link href="https://henry.catalinismith.se/2025/03/24/immigrants-in-tech-oppose-tighter-citizenship-rules/" rel="alternate" type="text/html" title="Immigrants in Tech Oppose Tighter Citizenship Rules" />
      <published>2025-03-24T00:00:00+01:00</published>
      <updated>2025-03-24T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/03/24/immigrants-in-tech-oppose-tighter-citizenship-rules</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/03/24/immigrants-in-tech-oppose-tighter-citizenship-rules/"><![CDATA[<p>Got hold of a link to this open letter just now. It’s titled
<a href="https://docs.google.com/forms/d/e/1FAIpQLSfoF_Y2OQ1m6lm-E1TiVyXsgW5_ZS4D12mPc-unRMyEctcewg/viewform">Immigrants in Tech Oppose Tighter Citizenship Rules</a>
and it’s worth a look. If you moved to Sweden for a tech job you might want to
think about signing it. I sure have.</p>

<p>It’s about all this new anti-immigrant stuff that’s happening at the moment.
<a href="https://www.regeringen.se/contentassets/98678c2a40d64ccd9f16e6017923bbce/sou-20251-skarpta-krav-for-svenskt-medborgarskap.pdf">Skärpta krav för svenskt medborgarskap</a>
they’re calling it. All kinds of new rules to make it harder to get citizenship.</p>

<p>Big fan of the approach the letter takes. No moralising. Straight to business
talk about Sweden’s attractiveness as a destination for skilled labour. It makes
a very strong point that to eliminate everything that’s distinctive about
Sweden’s immigration system is to surrender its competitiveness on the global
market.</p>

<p>Something I’ve noticed over the years since moving here for work is that
Sweden’s super rich often use people like me as political props in campaigns I
disagree with for cuts to things like capital gains tax. Now suddenly here’s a
real grassroots issue. Something we’re actually raising ourselves. It will be
interesting to observe what level of passion those same wealthy Swedes show for
the topic this time around.</p>

<p>Probably don’t hold your breath. But do go and
<a href="https://docs.google.com/forms/d/e/1FAIpQLSfoF_Y2OQ1m6lm-E1TiVyXsgW5_ZS4D12mPc-unRMyEctcewg/viewform">sign the bloody thing</a>.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[Got hold of a link to this open letter just now. It’s titled Immigrants in Tech Oppose Tighter Citizenship Rules and it’s worth a look. If you moved to Sweden for a tech job you might want to think about signing it. I sure have.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">The Week 12 Boycott</title>
      <link href="https://henry.catalinismith.se/2025/03/21/week-12-boycott/" rel="alternate" type="text/html" title="The Week 12 Boycott" />
      <published>2025-03-21T00:00:00+01:00</published>
      <updated>2025-03-21T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/03/21/week-12-boycott</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/03/21/week-12-boycott/"><![CDATA[<p>There’s a nationwide boycott of big supermarket chains this week in Sweden and
it’s been great fun. Probably the only downside has been all the tedious opinion
pieces criticising it.</p>

<p>Seen a lot of people talking trash about it over the course of the week
actually. The critique mostly focuses on the possibility that the boycott might
not magically solve the problem in one fell swoop. And I think it’s really
normal for the prospect of a supermarket boycott to make absolutely anyone
uncomfortable. It’s not fun to confront how much structural power over society
has been centralised in this handful of organisations. And taking part isn’t
exactly easy either. Some places even have so little competition that
participating in the boycott means not buying groceries at all for a week.</p>

<p>So I think in a lot of cases people just don’t want these uncomfortable thoughts
in their heads. Then it’s easier if you tell yourself that actually the boycott
is stupid. Immediately you’re back in the comfort zone of having permission from
yourself to do absolutely nothing about the problem.</p>

<p>In practice the fact that it’s a little bit hard is part of the fun. I’ve been
all over Malmö this week visiting shops I’d have otherwise spent a lifetime
walking past and never seen inside. Two places we got food from were even
interviewed by Dagens Nyheter about the boycott –
<a href="https://www.dn.se/ekonomi/torghandlare-och-orientlivs-far-nya-kunder-manga-pratar-om-bojkotten/">Bagdad Kött and Möllevångstorget</a>
– and they both said they’ve noticed an uptick in business this week.</p>

<blockquote>
  <p>The big chains usually work with two or three wholesalers. In the oriental
food industry we have hundreds of wholesalers. So we can get better prices by
making them compete with each other.</p>
</blockquote>

<p>The fact that neither of the places DN interviewed were run by white Swedes is
no coincidence either. That’s a demographic group I haven’t seen behind the
counter of any of the new places I’ve been this week. Bit embarrassing for
Sweden’s ruling coalition of neoliberals and white supremacists that their fav
scapegoat ethnic group has been doing free market economics better than them all
this time.</p>

<p>Been really cool going in so many new places too. You really missed out if you
sat on the sidelines of this thing. Worth having a think about getting involved
next time. The fact that it’s not easy really is half the fun.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[There’s a nationwide boycott of big supermarket chains this week in Sweden and it’s been great fun. Probably the only downside has been all the tedious opinion pieces criticising it.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Survival Mode</title>
      <link href="https://henry.catalinismith.se/2025/03/14/survival-mode/" rel="alternate" type="text/html" title="Survival Mode" />
      <published>2025-03-14T00:00:00+01:00</published>
      <updated>2025-03-14T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/03/14/survival-mode</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/03/14/survival-mode/"><![CDATA[<p>Stopped using Copilot outside of work a few weeks ago. Coding’s begun to feel
fun again since making that change. I hadn’t even noticed the disappearance of
that joy until it started coming back either.</p>

<p>The honeymoon period with Copilot was so cool. It was exciting to be able to
blow through any fiddly little problem without needing to put in any effort. I
guess the novelty wore off.</p>

<p>It left behind a severe dependence on this new tool too. You get proper lazy
from relying on AI. The speed boost’s the selling point, and replacing your
thought process and research with AI suggestions accounts for most of that. The
lack of use is death for those skills.</p>

<p>Worse still, I noticed I was often less than proud of the work I was producing
with this thing. LLMs write HTML and CSS in particular with all the care and
precision of a skywriter pilot trying to hold in a poo. Bloody depressing to
have worked so hard to get particularly good at those and then see this trendy
new tool slowly steal that talent like the aliens in Space Jam and replace it
with mediocrity-as-a-service.</p>

<p>So then I was randomly pissing around with MacVim the other day and didn’t have
Copilot configured. And it was like “Wait, what’s going on? What’s this funny
feeling?”. Turned out to be happiness. The eureka moment came when I installed
the Vim Copilot plugin and the joy went away again.</p>

<p>From there the decision to cut this tool out of the creative process was
obvious. It’s dead similar to my relationship with creative mode in MineCraft
actually. I find instant access to every item and block really uninspiring and
massively prefer the chaos and struggle of survival mode. The struggle’s where
all the <em>meaning</em> is.</p>

<p>In creative mode you pick the blocks you need from the same huge menu as
everyone else. Optimising how fast you can work with that UI becomes part of
being “good” at the game. In survival mode you generally rummage through some
kind of warehouse or bunker full of storage chests that you’ve built. Every item
in those chests has a story behind it, so even the act of getting something out
of storage replenishes the world-building magic of the game. It’s slower but
without it the world feels sterile.</p>

<p>I got into this whole career by accident. I rediscovered coding sometime around
2004, found it really fun, and next thing I knew I was doing it all the bloody
time. The end of that fun was happening just as accidentally as its beginning,
and I’m glad I identified it early enough to address it. Copilot suggestions
will probably stay enabled during work hours for the time being while I rebuild
my independent problem-solving abilities. But it’s been switched off for weeks
now in everything else I do and it feels <em>amazing</em>.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[Stopped using Copilot outside of work a few weeks ago. Coding’s begun to feel fun again since making that change. I hadn’t even noticed the disappearance of that joy until it started coming back either.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Onshoring My DNS</title>
      <link href="https://henry.catalinismith.se/2025/03/09/onshoring-my-dns/" rel="alternate" type="text/html" title="Onshoring My DNS" />
      <published>2025-03-09T00:00:00+01:00</published>
      <updated>2025-03-09T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/03/09/onshoring-my-dns</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/03/09/onshoring-my-dns/"><![CDATA[<p>I was arsehole deep in American domain name services, I recently noticed.
Between Verisign, Tucows and Cloudflare I had rigged my digital identity to blow
in the event of any number of increasingly plausible crisis scenarios. I chose
to own this mistake and begin the work of fixing it.</p>

<p>First, I decided to stop trusting Verisign. For starters, the deregulatory
agenda of the new regime in Washington enables them to
<a href="https://domainnamewire.com/2024/11/06/trump-win-likely-seals-fate-of-com-prices/">increase the price of .com domains</a>.
I weighed this alongside the Obama-era precedent of ordering Verisign to
<a href="https://archive.is/8qNdX">seize .com domains</a> and decided it adds up to too
much long term risk.</p>

<p>How much might .com domains cost by 2030? What are the odds of an
“America-first” price hike targeting foreign registrants? How many plot twists
away from politically-motivated domain seizures are we really? I don’t know, and
that’s the point. I came to the conclusion that I prefer the known up-front cost
of a migration over the unknown open-ended cost of the growing uncertainty.</p>

<p>I looked into the governance model of
<a href="https://internetstiftelsen.se/">Internetstiftelsen</a>, who operate the .se
top-level domain, and I liked what I saw. They’re a public service organisation
who make important contributions to the Swedish technology sector and whose
<a href="https://internetstiftelsen.se/app/uploads/2024/04/arsredovisning-2023-internetstiftelsen.pdf">annual reports</a>
are available to read online. I’d like them a lot more if they could find a way
to <a href="https://archive.is/ca0Y6">make things right with the people of Nieue</a> but
otherwise I’m happy.</p>

<p>Then I checked out <a href="https://www.loopia.se/">Loopia</a>. They’re practically
synonymous with the web business in Sweden so it was the obvious first port of
call. They’ve been bought &amp; sold a lot but still seem to be
<a href="https://support.loopia.se/wiki/var-star-era-servrar/">running their servers in Sweden</a>
and <a href="https://team.blue/">the conglomerate they’re currently owned</a> by is
European.</p>

<p>Loopia’s DNS admin UI belongs in a museum but it gets the job done. I was able
to replace everything I was using Tucows and Cloudflare for with Loopia. Thanks
to their focus on Sweden their 2FA is just BankID too. Much prefer that over
some authenticator app from one of the big US tech firms.</p>

<p>The downside of switching TLDs was having to do an email address migration.
These are the absolute worst. It’s hours of logging into every account one by
one and trying to find the “update email” feature. There are a few categories of
these.</p>

<ol>
  <li>There’s a web form to update your email address. It sends a confirmation link
to the old email address and the update only finalises when you click it.</li>
  <li>The web form immediately updates your email address with no confirmation
step.</li>
  <li>There is no web form. You have to email support and complete an identity
check for them to update your address on your behalf.</li>
  <li>There is no web form. When you email support they update the email address on
your account immediately with no identity check.</li>
</ol>

<p>Most services are in category one. Far too many are in category four. The slow
process of finding out which category you’re dealing with as you log into each
account one by one gets old fast. Makes your hands hurt after a while too.</p>

<p>Feels good once it’s finished though. The simple satisfaction of honest hard
work. And one less loose end to tie down before the storm.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[I was arsehole deep in American domain name services, I recently noticed. Between Verisign, Tucows and Cloudflare I had rigged my digital identity to blow in the event of any number of increasingly plausible crisis scenarios. I chose to own this mistake and begin the work of fixing it.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Codeberg Is The Future</title>
      <link href="https://henry.catalinismith.se/2025/03/07/codeberg-is-the-future/" rel="alternate" type="text/html" title="Codeberg Is The Future" />
      <published>2025-03-07T00:00:00+01:00</published>
      <updated>2025-03-07T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/03/07/codeberg-is-the-future</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/03/07/codeberg-is-the-future/"><![CDATA[<p>GitHub’s been my most-used digital service for years. Even interviewed
(unsuccessfully) there once. To some extent it had become an online “home” for
me. You know that feeling when you’ve lived in the same place long enough that
you can walk into the bathroom in the dead of night and hit the light switch on
your first try with one effortless flick of your arm? That’s been my
relationship with GitHub.</p>

<p>Until now, anyway. Doesn’t make sense any more as a European to have your
digital centre of operations in America. So I found myself on the market for a
replacement. And bloody hell, what a replacement I found.</p>

<p><a href="http://codeberg.org/about">Codeberg</a> is everything we should have wanted GitHub
to be all along. It’s democratically governed. It’s a non-profit. It’s led by
its own community. It has a governance model that resembles a really mature and
successful free software project rather than a VC cap table.</p>

<p>This is way more important than it might first seem. There’s a lot of
<a href="https://berthub.eu/articles/posts/you-can-no-longer-base-your-government-and-society-on-us-clouds/">discourse right now about digital sovereignty in Europe</a>.
Codeberg’s democratic governance model has a key role to play in that, I think.
The simplistic fantasy of a for-profit European GitHub falls apart when you
explore how that company’s profit focus would force it to prioritise money over
so-called “soft” values like sovereignty.</p>

<p>We’re unfortunate enough to have a case study demonstrating this. There’s
another well-known alternative to GitHub, called
<a href="https://en.wikipedia.org/wiki/GitLab">GitLab</a>. And GitLab literally originated
in Ukraine. Then as the company grew, they moved their HQ to San Francisco in
search of ever faster growth. And so now for all their commercial success
they’re on the wrong side of the orange curtain. Oops!</p>

<p>So I was
<a href="https://mastodon.social/@henrycatalinismith/114110816349744816">really excited</a>
to <a href="https://join.codeberg.org/">join Codeberg e.V.</a> as a voting member and
financial contributor. You’re even reading this post on the
<a href="https://codeberg.page/">Codeberg pages</a> server right now. This is 100% the
future of free software and it feels cool as hell to play even a very small role
in helping make it happen.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="codeberg" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[GitHub’s been my most-used digital service for years. Even interviewed (unsuccessfully) there once. To some extent it had become an online “home” for me. You know that feeling when you’ve lived in the same place long enough that you can walk into the bathroom in the dead of night and hit the light switch on your first try with one effortless flick of your arm? That’s been my relationship with GitHub.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Shutting down 666a</title>
      <link href="https://henry.catalinismith.se/2025/01/12/shutting-down-666a/" rel="alternate" type="text/html" title="Shutting down 666a" />
      <published>2025-01-12T00:00:00+01:00</published>
      <updated>2025-01-12T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2025/01/12/shutting-down-666a</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2025/01/12/shutting-down-666a/"><![CDATA[<p>Since <a href="/2023/10/31/announcing-666a/">October 31st 2023</a> I’ve been providing a
work environment email service called <a href="https://666a.se/">666a</a>. On March 31st
2025 it’s shutting down. Today I’m beginning the process of winding it down
gently, and I wanted to post something explaining that process and why it’s
happening.</p>

<h2 id="immediate-changes">Immediate Changes</h2>

<p>The following are effective right now. They’ve already happened, in fact.</p>

<ol>
  <li>The SendGrid account is downgraded to the free tier.</li>
  <li>Sign ups are disabled on <a href="https://666a.se/">666a.se</a>.</li>
  <li>The translated Swedish legislation has been migrated to
<a href="https://lagstiftning.codeberg.page/">lagstiftning.codeberg.page</a>. This part
I’m particularly pleased with. I actually think it will be easier to maintain
this resource in the long term in this new form.</li>
</ol>

<p>With these changes in place, what’s left on 666a.se is the code for delivering
the email alerting service. Existing users are welcome to update their contact
details or even <a href="https://666a.se/delete">request a full account deletion</a> if
they wish to.</p>

<h2 id="february-1st-2025">February 1st 2025</h2>

<p>At the start of Feburary, the SendGrid downgrade will kick in. This probably
won’t have any major effect on the reliability of the service as we’re in the
relatively quiet early months of the year. But if there’s a busy day in February
or March, some email alerts are going to fail to send.</p>

<h2 id="march-31st-2025">March 31st 2025</h2>

<p>The idea is to keep the alerts running until the end of March so that you get
pretty much an entire quarter of the year of notice before they stop coming.
I’ve seen paid services close down and disappear faster than this and it feels
sufficient. March 31st is the final day, and here’s how it’s going to go down.</p>

<ol>
  <li>Delete the
<a href="https://github.com/henrycatalinismith/666a.se/blob/main/bin/backup.sh">rolling database backups I keep on my home server’s external USB</a>.</li>
  <li>Run
<a href="https://github.com/henrycatalinismith/666a.se/blob/main/lib/tasks/shutdown.rake"><code class="language-plaintext highlighter-rouge">shutdown:anonymize</code></a>
on my only remaining copy of the data to clean out the personally
identifiable information from it and make it privately archivable for myself.</li>
  <li>Scale down the Fly cluster to 0 instances and delete the storage volume. At
this point the service will become unrecoverable.</li>
  <li>Point the domain name at a static host containing a short explanatory notice
and 301 redirects for all the translated Swedish legislation URLs.</li>
</ol>

<p>After that I’ll probably leave the domain up to serve the redirects for a year
or two. I think people mostly share links to employment law with each other for
very situational reasons so the harm of breaking those links two or three years
later is going to be close to zero. Nice that the content’s new home is a free
subdomain which will hopefully outlast the maximum lifespan of a domain name
funded by an individual person.</p>

<h2 id="why-its-happening">Why It’s Happening</h2>

<p>It costs money to run a service like this, and money’s a little tighter here
this year. I’ve been looking through my recurring monthly expenses for things to
cut and 666a sticks out because at around 300kr per month it’s one of the larger
ones.</p>

<p>It also takes time to run a service like this, and I’m feeling a little poorer
on that front this year too. In June last year
<a href="/2024/06/06/about-arbetsmiljoverkets-new-webdiarium/">it randomly broke due to an update to the HTML on av.se</a>.
Who knows what they’ll ship this year that might break it again, or how long
it’ll take to fix next time, or what I might already be busy doing when it
happens.</p>

<p>Been a lot of fun building and operating this, even though Arbetsmiljöverket
should definitely just be delivering it themselves directly on
<a href="https://www.av.se/">av.se</a>. I’ve ended up effectively having my own personal
copy of their diarium database as a result of running this for the past year or
so and there’s probably some interesting and/or entertaining stuff in that
dataset which I’ll hopefully have time to dig out over the next couple of
months. It’d be cool to mark the final shutdown date with a bit of that.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[Since October 31st 2023 I’ve been providing a work environment email service called 666a. On March 31st 2025 it’s shutting down. Today I’m beginning the process of winding it down gently, and I wanted to post something explaining that process and why it’s happening.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Accidentally Sending Too Many HTTP Requests</title>
      <link href="https://henry.catalinismith.se/2024/11/30/accidentally-sending-too-many-http-requests/" rel="alternate" type="text/html" title="Accidentally Sending Too Many HTTP Requests" />
      <published>2024-11-30T00:00:00+01:00</published>
      <updated>2024-11-30T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/11/30/accidentally-sending-too-many-http-requests</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/11/30/accidentally-sending-too-many-http-requests/"><![CDATA[<blockquote>
  <p>This was originally posted at
https://666a.se/news/accidentally-sending-too-many-http-requests and has been
migrated here since for long-term hosting.</p>
</blockquote>

<p>Arbetsmiljöverket
<a href="https://henry.catalinismith.com/2024/06/06/about-arbetsmiljoverkets-new-webdiarium">updated their webdiairum back in June</a>.
When I
<a href="https://codeberg.org/henrycatalinismith/666a.se/commit/1c2e2cf20a9c6650c18a8b2d7020d82b30dda99b">updated 666a to work with their new system</a>,
I made a coding mistake. The impact of my mistake was that 666a began to send
more and more HTTP requests to Arbetsmiljöverket’s webdiarium with each day that
passed after the mistake.</p>

<p>This was the kind of mistake where it doesn’t have very much impact at first,
but the impact quietly grows and grows. These are the worst. They’re so easy to
miss because of how subtle they are on the day you ship them. And then by the
time you do notice them, they’ve grown into a monster. By the time I caught
this, it was causing something like 10000 extra HTTP requests per day.</p>

<p><img src="/2024/11/30/search-growth-bug@1252x656.webp" alt="Graph showing a surge in growth around June" /></p>

<p>It’s
<a href="https://codeberg.org/henrycatalinismith/666a.se/commit/68a7c459335021dfe63b8fc358f68be9b7aba8ae">fixed</a>
now. Sorry, Arbetsmiljöverket. I hope the system extra load didn’t cause any
trouble, although to be honest I doubt I’m the only one monitoring the
webdiarium in this way so perhaps it wasn’t even noticeable on your end. You can
<a href="mailto:henry@catalinismith.se">email me</a> any time if you ever need to have a
word with me about something like this.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[This was originally posted at https://666a.se/news/accidentally-sending-too-many-http-requests and has been migrated here since for long-term hosting.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/11/30/search-growth-bug@1252x656.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/11/30/search-growth-bug@1252x656.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Using AI to help humans translate Swedish labour laws</title>
      <link href="https://henry.catalinismith.se/2024/11/12/using-ai-to-help-humans-translate-swedish-labour-laws/" rel="alternate" type="text/html" title="Using AI to help humans translate Swedish labour laws" />
      <published>2024-11-12T00:00:00+01:00</published>
      <updated>2024-11-12T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/11/12/using-ai-to-help-humans-translate-swedish-labour-laws</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/11/12/using-ai-to-help-humans-translate-swedish-labour-laws/"><![CDATA[<p>Over on 666a I provide
<a href="https://666a.se/labour-law">English translations of Swedish labour laws</a>. For
the most part these are based on some translated PDFs that the Swedish
government sometimes publish. The government update the laws more frequently
than they update the translations though. Providing an up to date resource here
meant updating the translations myself, which is a challenging problem.</p>

<p>I used a little bit of AI as part of the solution. But that doesn’t mean these
are “AI translations”. Christ no.</p>

<p><img src="/2024/11/12/donotwant@420x315.webp" alt="Still from Star Wars bootleg &quot;The Backstroke of the West&quot; with Darth Vader and the caption &quot;Do Not Want&quot;" /></p>

<p>What’s interesting about translating legal text in particular is that you really
<em>want</em> lots of uniformity in the outcome. If a particular phrase has been
translated a particular way nine times before then the tenth translation should
follow the pattern too. This means it’s not enough to speak Swedish.</p>

<p>Familiarity with the source text and the existing translations is key. In
practice, translating any given sentence becomes a long process of searching the
already-translated text for similar words and then reading those existing
translations to build an understanding of the prior context. Where I found a
good role for AI was in optimising that process, enabling me to spend less time
on the manual searching and more time on the reading and understanding.</p>

<p>Take the second paragraph of section 13 of the Working Hours Act for example.</p>

<table>
  <thead>
    <tr>
      <th>Swedish</th>
      <th>English</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>I den dygnsvila som alla arbetstagare har rätt till skall tiden mellan midnatt och klockan 5 ingå. Avvikelse får göras, om arbetet med hänsyn till dess art, allmänhetens behov eller andra särskilda omständigheter måste bedrivas mellan midnatt och klockan 5. Lag (2005:165).</td>
      <td>The daily rest period that all employees are entitled to shall include the hours between midnight and 05.00. Derogations may be made if the work, in view of its nature, the needs of the general public or other special circumstances, must be carried out between midnight and 05.00. Act (2005:165).</td>
    </tr>
  </tbody>
</table>

<p>First, I
<a href="https://codeberg.org/henrycatalinismith/666a.se/src/branch/main/app/jobs/labour_law/element_sentences_job.rb">use ChatGPT to break this up into sentence pairs</a>.</p>

<table>
  <thead>
    <tr>
      <th>Swedish</th>
      <th>English</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>I den dygnsvila som alla arbetstagare har rätt till skall tiden mellan midnatt och klockan 5 ingå.</td>
      <td>The daily rest period that all employees are entitled to shall include the hours between midnight and 05.00.</td>
    </tr>
    <tr>
      <td>Avvikelse får göras, om arbetet med hänsyn till dess art, allmänhetens behov eller andra särskilda omständigheter måste bedrivas mellan midnatt och klockan 5.</td>
      <td>Derogations may be made if the work, in view of its nature, the needs of the general public or other special circumstances, must be carried out between midnight and 05.00.</td>
    </tr>
  </tbody>
</table>

<p>Then a little more
<a href="https://codeberg.org/henrycatalinismith/666a.se/src/branch/main/app/jobs/labour_law/sentence_phrases_job.rb">ChatGPT to mulch those sentence pairs down further into pairs of words and phrases</a>.</p>

<table>
  <thead>
    <tr>
      <th>Swedish</th>
      <th>English</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>dygnsvila</td>
      <td>daily rest period</td>
    </tr>
    <tr>
      <td>som alla arbetstagare har rätt till</td>
      <td>that all employees are entitled to</td>
    </tr>
    <tr>
      <td>Avvikelse får göras</td>
      <td>Derogations may be made</td>
    </tr>
    <tr>
      <td>allmänhetens behov</td>
      <td>the needs of the general public</td>
    </tr>
  </tbody>
</table>

<p>Once we have those word/phrase pairs generated for all existing translations, we
can have a
<a href="https://codeberg.org/henrycatalinismith/666a.se/src/branch/main/app/views/rails_admin/main/element_translation_suggestions.html.erb">page that automatically lists matches for each word in a given paragraph of source text</a>.</p>

<p><img src="/2024/11/12/element_translation_suggestions@1788x2192.webp" alt="Website admin UI screenshot showing many word/phrase pairs" /></p>

<p>The results aren’t perfect, and what’s great is that they don’t need to be.
Sometimes the AI fucks up the word/phrase pairs, and it doesn’t matter. Since
we’re not removing humans from the process – but rather optimising how the
available time is used – it’s okay for this to be a little bit sketchy.</p>

<p>What this enables is to pick out any new paragraph that needs translating and
instantaneously scan the entire corpus of existing translations for the bits
that might be of interest to the human about to produce the new translation.
Even after visually scanning the results one time you immediately start to feel
a bit confident that you know how you’re going to translate some of the
legalistic glue phrases. It frees up your remaining attention and time to focus
on translating anything that’s actually novel about the new paragraph.</p>

<p>In the end I’m not sure if I saved time from this or just spent the same amount
of time more impactfully. With the correctness of legal text having such an
obvious importance, you end up <em>wanting</em> to invest a certain amount of love into
the work just for a sense of hygiene about the outcome. Like if it’s too quick,
it just doesn’t feel done. So in this case I think it’s more likely that using
AI just made the work more enjoyable and improved the outcome rather than
bringing it to an end sooner. Perhaps that’s not the standard capitalist
narrative about what this technology’s benefits are for society, but I’ll take
it.</p>

<p>Really happy with this. The level of hype around AI is nauseatingly intense and
that’s leading to a lot of overapplicaton of the tech. There’s a decent amount
of people using it to fully communicate on their behalf, which I really don’t
get. It has such a distinctive tone of voice with all those extra adjectives and
adverbs, and that uncanny beehive-looking regularity in the structure of the
sentences and paragraphs. It’s amazing to me that people either don’t see how
obvious it is or don’t feel how unengaging it is once you twig that you’re
reading something that wasn’t important enough to write. Negative experiences
like those leave the door wide open to outright cynicism about the tech in
general. I’m glad I have a nice example like this to keep my head balanced.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[Over on 666a I provide English translations of Swedish labour laws. For the most part these are based on some translated PDFs that the Swedish government sometimes publish. The government update the laws more frequently than they update the translations though. Providing an up to date resource here meant updating the translations myself, which is a challenging problem.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/11/12/donotwant@420x315.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/11/12/donotwant@420x315.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">“All we know about is computers”</title>
      <link href="https://henry.catalinismith.se/2024/11/06/all-we-know-about-is-computers/" rel="alternate" type="text/html" title="“All we know about is computers”" />
      <published>2024-11-06T00:00:00+01:00</published>
      <updated>2024-11-06T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/11/06/all-we-know-about-is-computers</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/11/06/all-we-know-about-is-computers/"><![CDATA[<video controls="" preload="none" poster="/2024/11/06/all-we-know-about-is-computers@512x288.webp">
  <source src="/2024/11/06/all-we-know-about-is-computers@512x288.mp4" type="video/mp4" />
</video>

<p>Recently I did this little panel discussion at Unionen HQ about union organising
in the Swedish tech industry. It was great fun. We talked for ages and everyone
said lots of cool stuff. I think this was the most important thing I said.</p>

<blockquote>
  <p>There is a sort of helplessness… like a learned helplessness. There’s this
idea that okay, these other workers in this industry that industry and that
industry they all got like union deals, collective agreements at their jobs
through conflict and so on back in the 20th century. But that was then. This
is now. That was those industries. We can’t do that because all we know about
is computers and that was all a long time ago. It bothers me this idea that
“oh <em>we</em> can’t do it, <em>we</em> can’t have that”.</p>

  <p>Yes we can. You can just do it. You just start. Just start. That’s all. As
long as you start, you can have it. The trick is just to not let anyone talk
you out of starting that’s all. Because there’s a lot of voices out there who
would like to talk you out of starting. They’ll tell you that’s not for us, we
can’t have that.</p>

  <p>Don’t let them tell you what you can achieve, you know. You can have this.</p>
</blockquote>

<p>I also fell in love with the accidental comedy of this one frame from the video.</p>

<p><img src="/2024/11/06/all-we-know-about-is-computers@256x256.webp" alt="still from video with a sad face and the caption &quot;all we know about is coputers&quot; superimposed" /></p>

<p>Captures perfectly how people are intended to feel when their anti-union
employer tells them
<a href="https://lagstiftning.codeberg.page/codetermination-act/2021:1114/section-11">MBL § 11</a>
negotiations would slow things down too much. Unionen’s
<a href="https://www.frihet.unionen.se/en">frihet.unionen.se</a> campaign is worth a look
if that sounds like your workplace BTW.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/11/06/all-we-know-about-is-computers@512x288.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/11/06/all-we-know-about-is-computers@512x288.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Kubernetes homelab on an old MacBook Air</title>
      <link href="https://henry.catalinismith.se/2024/10/01/kubernetes-homelab-on-an-old-macbook-air/" rel="alternate" type="text/html" title="Kubernetes homelab on an old MacBook Air" />
      <published>2024-10-01T00:00:00+02:00</published>
      <updated>2024-10-01T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2024/10/01/kubernetes-homelab-on-an-old-macbook-air</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/10/01/kubernetes-homelab-on-an-old-macbook-air/"><![CDATA[<figure>
  <img src="/2024/10/01/kubernetes-homelab-on-an-old-macbook-air@1024x768.webp" alt="Router, laptop and other computer equipment sitting together on a shelf" />
</figure>

<p>Reflecting on
<a href="https://henry.catalinismith.com/2024/08/18/ergodox-ten-years-later/">the ten year milestone of using my Ergodox</a>
the other day really got me thinking. Tech’s so dominated by consumerism. It’s
all very American really. Bigger is better and all that. Silly.</p>

<p>So instead here’s a run through of quite a realistic, minimal homelab setup. It
lives on the shelf above our front door. And it’s grown organically over time,
through reusing old equipment and avoiding buying loads of expensive new stuff.</p>

<h2 id="modem">Modem</h2>

<p>The big chunky boy on the left is our modem. This belongs to our ISP, and isn’t
very good. I keep it standing up on its side like that so I can see the status
lights when the connection drops out. That happens a lot.</p>

<p>We actually only have one choice of ISP in our building. Recently it came out in
the news that
<a href="https://www.dn.se/ekonomi/kickbacks-omsatter-miljarder-hamnar-pa-hyresgasternas-bredbandsnota/">landlords in Sweden are getting kickbacks from ISPs</a>
for monopolies like this one. So maybe that’s why.</p>

<p>Anyway, its range isn’t good enough to get a WiFi signal to any of the parts of
the apartment where it’s needed. So it’s bridged to the Google Nest WiFi router
next to it.</p>

<h2 id="router">Router</h2>

<p>This Google Nest WiFi thing is practically a member of the family. It floods the
entire apartment with glorious internet. “Hey Google, stop” were some of the
kids’ first words.</p>

<p>Sometimes I wonder what it must be like to grow up with a talking computer. What
it must do to your expectations about what it’s possible for the computer to do.
Then I find myself doing a sarcastic impersonation of an American because it
can’t understand “Hey Google, play BBC Radio Merseyside” unless you say it like
someone from California. Never mind.</p>

<p>It has a little ethernet port underneath, which is plugged into our NAS.</p>

<h2 id="nas">NAS</h2>

<p>Way back in the old country I had one of those AirPort Time Capsule things that
Apple don’t make any more. There wasn’t room for it when I packed to move to
Sweden, so it went in a cupboard in my parents’ house for a good six or seven
years until they found it one day.</p>

<p>It’ll be about ten years old soon and it still works. Homelab nerds would
probably cringe at the thought of using such an old single disk NAS. And sure,
it’ll probably die soon. But we had zero disks before this showed up, and that
was basically fine. Not trying to compete with the Arctic World Archive here.</p>

<h2 id="server">Server</h2>

<p>At some point in like 2021 I went to play
<a href="https://open.spotify.com/album/3Nunc0C2S3K5heQ9VRQFPy">Casual Sex In The Cineplex</a>
on Spotify and it said “This content is not available”. Still does today,
actually.</p>

<figure>
 <img alt="Spotify screenshot where the tracks in the album Casual Sex In The Cineplex by Sultans of Ping FC are greyed out and a toast notification saying 'This content is not available' is displayed" src="/2024/10/01/not-available@1146x1224.webp" />
 <figcaption>Not available my arse.</figcaption>
</figure>

<p>Anyway something snapped in me that day. A real “Hey, that’s not the wallet
inspector” moment. I set up Plex on my laptop and put the album on there
instead. After a few days I realised this was something I was probably going to
keep using, so I dug out my old 2013 MacBook Air and moved the Plex server over
onto that. And that laptop has been a Plex server ever since.</p>

<p>Those old MacBooks didn’t come with tons of storage, so it didn’t take long to
fill up. Eventually I bought a 1TB external SSD. It still runs off that today.
Sometimes it fills up and I’ll delete a show or a film to make space. Over time
I upgraded it to a nice Docker Compose &amp; Traefik setup with some help from a
friend. Then later I installed Minikube and upgraded it to Kubernetes.</p>

<p>Old laptops are underrated homelab servers. For one, the battery means it
doesn’t immediately die if the power goes out. That’s a built-in UPS! Even
better though are the built-in speakers. If I’m away on a work trip I’ll often
SSH into it while I’m FaceTiming with the kids and run
<code class="language-plaintext highlighter-rouge">say -vDaniel -r90 "Piss off naughty boys"</code>. I see a lot of very fancy rackmount
homelab clusters on YouTube and they never have speakers. Not one of them can
deliver the joy of watching two little boys running around laughing and shouting
“No <em>you</em> piss off Google”.</p>

<h2 id="oh-yeah-kubernetes">Oh yeah, Kubernetes</h2>

<p>Sorry, you’re probably here from Google about the Kubernetes MacBook Air homelab
thing aren’t you. Let’s get into it then.</p>

<p>I’m running Minikube on this thing. It’s a fairly standard traefik &amp;
cert-manager setup. I’ve stopped bothering with setting up an automated dynamic
DNS thing, to be honest. My modem’s public IP changes rarely enough that I don’t
mind just logging into Cloudflare and updating it once every few weeks.</p>

<p>That’s it though. It seems a bit maxed out with just that workload. I tried to
spin up Authentik on it once and the whole thing kind of died on its arse. CPU
fans roaring, pods getting evicted left and right, that kind of thing. It’s
gonna need replacing with something proper before I can do more with it. So if
you’re here from Google hoping to learn about running a badass k8s cluster on
your old MacBook Air, too bad. Can’t be done, sorry. But do give “Piss off
naughty boys” a try sometime.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/10/01/kubernetes-homelab-on-an-old-macbook-air@1024x768.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/10/01/kubernetes-homelab-on-an-old-macbook-air@1024x768.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Ergodox, Ten Years Later</title>
      <link href="https://henry.catalinismith.se/2024/08/18/ergodox-ten-years-later/" rel="alternate" type="text/html" title="Ergodox, Ten Years Later" />
      <published>2024-08-18T00:00:00+02:00</published>
      <updated>2024-08-18T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2024/08/18/ergodox-ten-years-later</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/08/18/ergodox-ten-years-later/"><![CDATA[<figure>
  <img src="/2024/08/18/ergodox@1024x768.webp" alt="Ortholinear split keyboard" />
</figure>

<p>It’s been about a decade since I
<a href="https://henry.catalinismith.com/2014/11/16/that-weird-keyboard/">built my Ergodox</a>.
I’m still using it! I don’t think I’ve ever had anything I’ve used so frequently
last so long. Here’s everything I’ve learned about it. A little hardware review
after ten years of almost daily use.</p>

<h2 id="blank-keycaps">Blank Keycaps</h2>

<p>The blank keycaps are fine for most typing. The layout becomes muscle memory
really quickly. The fact that they remove any reason to stare down at the
keyboard while typing is probably a good thing.</p>

<p>But blank keycaps are terrible for entering long numerical sequences such as
credit card numbers, one-time passwords, phone numbers and so on. A decade in
and I’m still unable to do this kind of thing without making mistakes. Printed
keycaps are a lot better for this.</p>

<p>I chose blank keycaps because I didn’t want to limit my layout options. In
practice this wouldn’t have been a problem. On balance I’m still happy with this
choice, but it would have been fine either way.</p>

<h2 id="mx-clear-switches">MX Clear Switches</h2>

<p>I love typing on this keyboard, and the switches are why. MX clears push back a
bit. It makes the whole keyboard feel expensive and sturdy. They’re nice and
quiet too, by mechanical keyboard standards.</p>

<p>For a short while, I had a Moonlander. It came with MX browns, which I chose
because that was the closest they had to clears. I didn’t like it quite as much,
but it was still <em>fine</em>. I rarely noticed the difference. People stress too much
about choosing switches, I think.</p>

<h2 id="acrylic-case">Acrylic Case</h2>

<p>The acrylic case does a great job of damping the typing noise. During my short
time with the Moonlander, I found it very loud in comparison to the Ergodox, and
I think the case is partly the reason for the difference. The Ergodox’s acrylic
case engulfs the switches from all sides, whereas the Moonlander’s more
minimalist chassis exposes them. Does make cleaning the Ergodox a little more
troublesome in comparison to the Moonlander, though.</p>

<p>Once a year I take the case apart and clean everything out. One year, I fucked
up and broke one of the layers of the case during this process. Due to the open
source nature of the Ergodox hardware design it was easy to find
<a href="https://falbatech.click/products/One-Hand-ErgoDox-Standard-Acrylic-Case-Colorless-p613842440">a replacement</a>.</p>

<h2 id="keyboard-layout">Keyboard Layout</h2>

<p>Four of the thumb cluster keys are hard to reach. Back in 2014 I mapped a bunch
of punctuation characters to those keys. In the years since, I moved all that
stuff back to more standard positions. This is an ergonomic detail ZSA have
gotten 100% right in the Moonlander, where they’ve completely removed these keys
from each thumb cluster. They’re just not useful.</p>

<p>Still very happy with having the escape key right there in the thumb cluster.
Such an underrated key. You’re probably pressing it more often than you realise.</p>

<h3 id="original-layout-from-2014">Original layout from 2014</h3>

<figure>
 <img alt="" src="/2024/08/18/ergodox-layout-2014@700x277.webp" />
 <figcaption>
 </figcaption>
</figure>

<h3 id="current-layout-in-2024">Current layout in 2024</h3>

<figure>
 <img alt="" src="/2024/08/18/ergodox-layout-2024@2835x1174.webp" />
 <figcaption>
 </figcaption>
</figure>

<h2 id="software">Software</h2>

<p>The screenshot from 2014 there is of a layout configuration tool created by a
company called Massdrop. I bought the parts for my Ergodox from them, and they
provided an online configuration tool as part of their promotional campaign for
the kit. The 2024 screenshot looks so different because that original tool no
longer exists. It was a proprietary tool hosted by Massdrop, and at some point
they took it offline. The company’s not even called Massdrop any more, in fact.</p>

<p>Fortunately, ZSA sell an Ergodox-based product called the Ergodox EZ, and so
they created
<a href="https://configure.zsa.io/ergodox-ez/layouts/default/latest/0">their own layout configuration tool</a>
for that. And mostly thanks to the Ergodox firmware being free software, that
tool is compatible with older models like mine.</p>

<h2 id="repairs--longevity">Repairs &amp; Longevity</h2>

<p>The TRRS cable that connects the two halves of the Ergodox requires more force
to plug in and unplug than e.g. a USB C cable. That force puts a bit of strain
on the solder joints holding the TRRS jack onto the PCB.</p>

<p>After many years of use, this strain wore out the solder joint, and the
connection between the two halves of the keyboard became unreliable. That was
what led me to try a Moonlander for a while. Eventually it felt like a missed
opportunity that I’d bought a new thing instead of repairing my Ergodox, so I
sold the Moonlander and fired up the soldering iron.</p>

<p>It was an easy 30 minute job to fix it, too. It was enough to heat the solder up
and gently push the TRRS jack back into place. After fixing it, I also replaced
the original TRRS cable with a
<a href="https://falbatech.click/products/Braided-Cable-TRRS-Jack-Jack-1-m-p613853174">coiled one</a>.
It feels a little bit gentler on that solder joint.</p>

<p>It’s a rare pleasure to have a piece of computer equipment last an entire decade
of almost daily use. There’s no reason it can’t last another decade, either. As
long as I keep it out of reach of the kids.</p>

<h2 id="successful-rsi-treatment">Successful RSI Treatment</h2>

<p>When I first bought it, I was wary of it becoming a gateway into a materialistic
hobby. Never been a fan of the “basically just buying things” category of
hobbies. Nerd culture is prone to a really shallow and very capitalist form of
individualism based on defining yourself primarily in terms of what kind of
labour you sell, who you sell it to, and what specific treats and goodies you
buy with the leftovers of the wage you receive for that labour.</p>

<p>I think a far more compelling vision of individual empowerment involves working
together to eliminate the barriers that turn people’s identities and bodies into
limiting factors in their own lives. That’s why I try not to forget that my
Ergodox is actually a piece of assistive technology that I bought to save my
tech career from ending early due to wrist pain. It’s succeeded wonderfully at
this, and the strong copyleft license of the hardware design &amp; firmware seems to
be doing a great job of ensuring it’s a lasting solution as well.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="hardware" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/08/18/ergodox@1024x768.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/08/18/ergodox@1024x768.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Goodbye Jane</title>
      <link href="https://henry.catalinismith.se/2024/07/27/goodybe-jane/" rel="alternate" type="text/html" title="Goodbye Jane" />
      <published>2024-07-27T00:00:00+02:00</published>
      <updated>2024-07-27T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2024/07/27/goodybe-jane</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/07/27/goodybe-jane/"><![CDATA[<figure>
  <img src="/2024/07/27/core-fundamentals@672x477.webp" alt="
      ORGANIZING FOR POWER
      THE CORE FUNDAMENTALS
      Graduation Certificate
      February 8 - March 15, 2023
      Presented to Henry Catalini Smith
      This certificate recognizes successful completion of the six-week intensive course 'The Core Fundamentals', part of the 'Organizing for Power' training program for organizers worldwide.
      Jane McAlevey, Lead Trainer
      Ethan Earle, Program Coordinator
      THE WORKING CLASSES IN EVERY COUNTRY ONLY LEARN TO FIGHT IN THE COURSE OF THEIR STRUGGLES. - ROSA LUXEMBURG
    " />
</figure>

<p>After attending today’s Organizing For Power memorial to mark the passing of
lead trainer <a href="https://en.wikipedia.org/wiki/Jane_McAlevey">Jane McAlevey</a>, I
decided to put my graduation certificate online as a little personal tribute and
share some memories and hard-earned lessons of my own.</p>

<p>I discovered Jane’s work at an organizer training event hosted by Vänsterpartiet
Malmö, and by the time the course began I’d already read all her books. Getting
to participate in a “fishbowl” exercise with her during one of the training
sessions is a deeply cherished memory.</p>

<p>One of my few certainties about the Spotify campaign is that every aspect of it
that fell short of my expectations can be understood in terms of this
methodology and all the situations where enacting it would have produced better
outcomes. It’s a lesson I take every opportunity I get to pass on to other
fledgling organizers.</p>

<p>Jane’s belief in workers and their ability to win remains contagious beyond her
death. The anecdotes at today’s memoral about her commitment to raising up those
around her filled me with determination of my own. At Spotify, you normally
couldn’t get the bosses to shut up about the importance of a “growth mindset”. I
still remember how jarring it felt in the
<a href="https://en.wikipedia.org/wiki/Captive_audience_meeting">captive audience meetings</a>
when they flipped that around 180 degrees to explain that the inexperience of
the union representatives there would permanently hinder their everyday change
management work if a union deal imposed an obligation to negotiate under
<a href="https://lagstiftning.codeberg.page/medbestammandelagen/2021:1114/section-11/">MBL § 11</a>.
I take every opportunity I get to remind my former colleagues that they don’t
have to only have a growth mindset when the bosses tell them to. And it’s a
great inoculation topic that I always recommend organizers elsewhere not to skip
over.</p>

<p>Jane’s work rate and sense of urgency was almost legendary. I like
<a href="https://www.rosalux.de/en/news/id/51885/remembering-jane-mcalevey-1964-2024">Ethan Earle’s take on this</a>
most of all.</p>

<blockquote>
  <p>For foes and friends alike, Jane had something of a magical aura about her.
That said, she always sought to shed that perception. Everything she did was
the result of hard work and practice — and all of it can be reproduced by
those willing to put in the time that she did.</p>

  <p>So, read her books and take her trainings, but not to deify her — nothing
could be further from her mission. Take them so that you can put into practice
the same methods that Jane McAlevey spent a lifetime practicing, modelling,
and instilling in others. And then, as she would so often say at the end of a
session: Go forth and win!</p>
</blockquote>

<p>In truth, I’ve noticed a similar intensity of spirit in just about everyone I’ve
met in the labour movement. And if you’re in tech, given that you’re surviving
this industry’s treadmill of eternal retraining as your old knowledge races ever
faster towards obsoletion, you can take it as read that you have what it takes
to learn how to build collective power and win.</p>

<p>The next Organizing For Power training program begins in February 2025. If you
can put together a group of 10 or more in time for registration in December, you
can get yourselves the best organizer training around and max out your chances
of winning your own campaign. Go ahead and
<a href="https://docs.google.com/forms/d/e/1FAIpQLSfWM4YC-jzgo1ISyZaSJiCgFK2wdFy6DvPgyL5pUliuvKXLQA/viewform">pre-register now</a>
and you’ll be one step along the way already.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/07/27/core-fundamentals@672x477.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/07/27/core-fundamentals@672x477.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">All laws now up to date!</title>
      <link href="https://henry.catalinismith.se/2024/06/25/all-laws-now-up-to-date/" rel="alternate" type="text/html" title="All laws now up to date!" />
      <published>2024-06-25T00:00:00+02:00</published>
      <updated>2024-06-25T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2024/06/25/all-laws-now-up-to-date</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/06/25/all-laws-now-up-to-date/"><![CDATA[<blockquote>
  <p>This was originally posted at https://666a.se/news/all-laws-now-up-to-date and
has been migrated here since for long-term hosting.</p>
</blockquote>

<p>A few weeks ago I mentioned that preparations for
<a href="https://henry.catalinismith.com/2024/06/11/lets-update-the-english-translation-of-the-work-environment-act/">updating the English translation of the Work Environment Act</a>
were complete. Even more good news now: not only is 666a’s
<a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/">English translation of the Work Environment Act</a>
now completely up to date, but so is the
<a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/">English translation of the Working Hours act</a>!</p>

<p>As far as I’m aware, right now 666a is now the only place on the web where you
can get an up-to-date
<a href="https://lagstiftning.codeberg.page/">English translation of Sweden’s four most important labour laws</a>.
The ones on government.se are still out of date. And in the case of the Work
Environment Act, more than a decade out of date.</p>

<p>My favourite thing I learned in the process of updating these translations was
in
<a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-7-section-16/">Chapter 7 Section 16 of the Work Environment Act</a>.</p>

<blockquote>
  <p>The market surveillance authority may procure a sample of goods under a hidden
identity according to Article 14.4 j of Regulation (EU) 2019/1020 only if
necessary to achieve the purpose of the inspection.</p>
</blockquote>

<p>It grants Swedac the power to order stuff under false names in order to verify
compliance with quality and safety requirements. This is an implementation of an
EU law, and it just stuck in my head because you don’t tend to think of quality
assurance as a form of espionage, but it actually makes a huge amount of sense
when you think about it!</p>

<p>Also found it interesting because I don’t think it really seems to belong in the
Work Environment Act at all. It certainly has nothing to do with anyone’s work
environment. I think it seems to have been shoehorned in here for convenience
due to its similarities with e.g.
<a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-7-section-5/">Chapter 7 Section 5</a>,
maybe to avoid the work of creating a separate Market Surveillance Act or
something. I think software engineers have more in common with the people
writing laws than any of us might have thought!</p>

<p>Next up, I think I’d like to rescue the Parental Leave Act and the Annual Leave
Act from the Swedish government’s PDF compost heap. There’s nearly a decade’s
worth of updates missing from the most recently published English translation of
the Parental Leave Act too, so that one’s going to take a minute or two to get
polished and shipped on here.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[This was originally posted at https://666a.se/news/all-laws-now-up-to-date and has been migrated here since for long-term hosting.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Let’s update the English translation of the Work Environment Act</title>
      <link href="https://henry.catalinismith.se/2024/06/11/lets-update-the-english-translation-of-the-work-environment-act/" rel="alternate" type="text/html" title="Let’s update the English translation of the Work Environment Act" />
      <published>2024-06-11T00:00:00+02:00</published>
      <updated>2024-06-11T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2024/06/11/lets-update-the-english-translation-of-the-work-environment-act</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/06/11/lets-update-the-english-translation-of-the-work-environment-act/"><![CDATA[<blockquote>
  <p>This was originally posted at
https://666a.se/news/lets-update-the-english-translation-of-the-work-environment-act
and has been migrated here since for long-term hosting.</p>
</blockquote>

<p>The original
<a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349">English translation of the Work Environment Act on 666a</a>
was sourced from the
<a href="https://www.government.se/government-policy/labour-law-and-work-environment/19771160-work-environment-act-arbetsmiljolagen/">government-produced translation on government.se</a>.
That’s a translation of a decade-old version of the law, so until today there
was a big red warning at the top of every page on 666a saying “watch out, this
is out of date”. Not any more.</p>

<p>Over the past several months I’ve looked very carefully through every paragraph
of every section of every chapter of this law. Most of it is the same as it was
in the version the government translated back in 2014 or so. So for those, I’ve
kept the original English translation and removed the warning.</p>

<p>In cases where the wording of a section of the law has changed, I’ve updated the
Swedish text to match the latest version and removed the English translation
entirely. The missing English text is replaced with links to GitHub issues where
contributors (like you reading this, maybe) are invited to collaborate in public
on updating the translations. Those pages are</p>

<ul>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-1-section-2c">Chapter 1 Section 2c</a></li>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-1-section-4">Chapter 1 Section 4</a></li>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-2-section-8">Chapter 2 Section 8</a></li>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-2-section-10">Chapter 2 Section 10</a></li>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-6-section-17">Chapter 6 Section 17</a></li>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-9-section-2">Chapter 9 Section 2</a></li>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-9-section-5">Chapter 9 Section 5</a></li>
</ul>

<p>And in cases where completely new sections have been added, I’ve done a similar
thing. Those are</p>

<ul>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-3-section-8a">Chapter 3 Section 8a</a></li>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-7-section-15">Chapter 7 Section 15</a></li>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-7-section-16">Chapter 7 Section 16</a></li>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-7-section-17">Chapter 7 Section 17</a></li>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-7-section-18">Chapter 7 Section 18</a></li>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-7-section-19">Chapter 7 Section 19</a></li>
  <li><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-7-section-20">Chapter 7 Section 20</a></li>
</ul>

<p>I don’t think it’s terribly complicated to translate these few words from
Swedish to English. Especially not with so much reference material to work with.
The vast majority of the vocabulary in a given paragraph of one of these laws
overlaps massively with the vocabulary in the rest of the law.</p>

<p>What I do think is important and challenging, though, is for the translations to
feel trustworthy. And for that I think it’s important that the work of producing
them should be methodical, accountable, and public. So that’s why I’ve set it up
as GitHub issues to start with.</p>

<p>In the long term, I’m interested in incorporating this translation process
directly into 666a as a first-class citizen feature on equal footing with the
email alerts and the labour laws. Requiring a GitHub account might exclude
contributors for one thing. But more than that I think there’s opportunity in
the tooling itself. For example, one thing we’d produce would be a glossary of
common legal words, and I think that could be a valuable resource in itself once
completed.</p>

<p>But enough daydreaming. What’s important right now is that the horrible red “out
of date” warning is gone from
<a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/">the Work Environment Act translation on here</a>.
And there’s now at least <em>somewhere</em> online where the work of updating these
translations might actually happen. That’s probably our strongest chance of
advancing this form of integration at the moment, given the “Ausländer Raus”
mentality that’s currently so popular in Sweden’s national parliament.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[This was originally posted at https://666a.se/news/lets-update-the-english-translation-of-the-work-environment-act and has been migrated here since for long-term hosting.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">We maxed out Sendgrid’s free tier</title>
      <link href="https://henry.catalinismith.se/2024/06/10/we-maxed-out-sendgrids-free-tier/" rel="alternate" type="text/html" title="We maxed out Sendgrid’s free tier" />
      <published>2024-06-10T00:00:00+02:00</published>
      <updated>2024-06-10T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2024/06/10/we-maxed-out-sendgrids-free-tier</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/06/10/we-maxed-out-sendgrids-free-tier/"><![CDATA[<blockquote>
  <p>This was originally posted at
https://666a.se/news/we-maxed-out-sendgrids-free-tier and has been migrated
here since for long-term hosting.</p>
</blockquote>

<p>Last week, 666a’s daily email volume exceeded the limits of Sendgrid’s free
tier. This caused a brief email alert service outage, as a number of email
notifications were blocked from sending. This is now fixed: I’ve upgraded 666a’s
Sendgrid account to the $19.95/month billing tier and re-sent the email alerts
that were initially affected by the issue.</p>

<p>This is a total lyxproblem, and a milestone I’ve been anticipating for a while.
666a’s admin section has some email usage graphs which include daily sending
statistics, so it’s been possible to observe the upwards trend in the daily
email volume.</p>

<p>Sendgrid performs some kind of background check on your account before they’ll
let you upgrade your billing tier and start sending larger volumes of email. I’d
already completed this process in advance, months ago in fact, because the idea
was to avoid an outage entirely and simply upgrade ahead of time shortly before
hitting the limit.</p>

<p>I’d calculated that I had another month or two before it would become necessary
to pay. What I hadn’t counted on was that my cleanup work after the new
Arbetsmiljöverket Webdiarium would be enough to tip us over the limit ahead of
schedule. I try really hard to keep this thing running as reliably as possible
and dropped the ball very slightly in this case.</p>

<p>The extra bill brings 666a’s monthly running costs to somewhere in the 300kr
range. It’s roughly triple what it was before, but still manageable. This
Sendgrid billing tier is overpowered for 666a’s current scale, so in the near
future I’ll be looking into cheaper suppliers with smaller limits.</p>

<p>Later, it might be interesting to investigate the possibility of asking one or
more of the central unions or umbrella organisations to help cover some of these
costs. First though, I think let’s get a whole first year of operation in the
rear view mirror, and then see how we’re looking in terms of statistics and
numbers.</p>

<p>That kind of funding will probably become more achievable the more impressive
the usage metrics sound. So if you want a more self-sustaining 666a then
spreading the word about the service to your own personal network within the
labour movement is probably a way you can help make it happen.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[This was originally posted at https://666a.se/news/we-maxed-out-sendgrids-free-tier and has been migrated here since for long-term hosting.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">About Arbetsmiljöverket’s New Webdiarium</title>
      <link href="https://henry.catalinismith.se/2024/06/06/about-arbetsmiljoverkets-new-webdiarium/" rel="alternate" type="text/html" title="About Arbetsmiljöverket’s New Webdiarium" />
      <published>2024-06-06T00:00:00+02:00</published>
      <updated>2024-06-06T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2024/06/06/about-arbetsmiljoverkets-new-webdiarium</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/06/06/about-arbetsmiljoverkets-new-webdiarium/"><![CDATA[<blockquote>
  <p>This was originally posted at
https://666a.se/news/about-arbetsmiljoverkets-new-webdiarium and has been
migrated here since for long-term hosting.</p>
</blockquote>

<p>The Work Environment Authority
<a href="https://www.av.se/nyheter/2024/ny-e-tjanst-for-att-soka-och-bestalla-handlingar/">shipped a completely new version of their web search tool last week</a>,
which took 666a’s email alerts out of action for a week. It’s fixed now.</p>

<p>This was always my nightmare scenario for 666a. The Work Environment Authority’s
search tool was already looking very old when I built 666a. There’s a particular
type of “looks old” where you can almost hear the team who maintain it arguing
internally for a rewrite.</p>

<p>My fear was that the new version, when it inevitably came, would require
JavaScript. The old version served results as plain server-rendered HTML, which
is important for a service like 666a because it means you can scrape them quite
efficiently with just an HTTP request and an HTML parser. The economics of
providing this kind of email alert service would look a lot worse if it became
dependent on headless browser automation due to a JavaScript-heavy rewrite of
the search tool.</p>

<p>Thankfully this hasn’t happened. And for that, I’m very grateful to whoever’s
behind this rewrite. Seriously, thank you.
<a href="https://www.av.se/om-oss/diarium-och-allmanna-handlingar/bestall-handlingar/">The new version</a>
is lovely, by the way, so great job!!</p>

<p>Now that 666a’s an open source project, the full incident response process is
public on GitHub. There’s even a project called “Update the email alerts service
for the Work Environment Authority’s new web search tool” where you can get an
overview of each individual task if you’re curious. Always happy to onboard new
contributors so if it sparks your curiosity do reach out!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[This was originally posted at https://666a.se/news/about-arbetsmiljoverkets-new-webdiarium and has been migrated here since for long-term hosting.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Eurovision Tourists Go Home</title>
      <link href="https://henry.catalinismith.se/2024/05/12/eurovision-week/" rel="alternate" type="text/html" title="Eurovision Tourists Go Home" />
      <published>2024-05-12T00:00:00+02:00</published>
      <updated>2024-05-12T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2024/05/12/eurovision-week</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/05/12/eurovision-week/"><![CDATA[<figure>
  <img src="/2024/05/12/IMG_0137@1024x1024.webp" alt="Graffiti mural reading 'Eurovision tourists GO HOME' with messages of solidarity for Gaza in smaller text around it." />
</figure>

<p>Eurovision’s a mess this year. The context everyone already knows is that the
Israeli occupation is using it to artwash their genocide of the people of Gaza.
There’s more to it than that, though. If the UK was hosting it this year, for
example, I doubt we’d be seeing such a high level of organised resistance.</p>

<p>You need to bear a few things in mind in order to fully understand
Eurovision 2024. For one, Eurovision is a huge deal here in Sweden. I don’t
think any other country takes the competition as seriously as Sweden does. It’s
a part of the national identity in a way that’s almost impossible for e.g. a
British person to understand. I’ve heard the atmosphere was great least year
when Liverpool hosted it. Now imagine The Beatles got their first big break at
Eurovision and that 2023 was its 50th anniversary, and try to picture how much
more intense the excitement would have been about hosting.</p>

<p>The local context is key too. Malmö is a special place, and it’s difficult to
imagine a worse choice of venue for this kind of artwashing event. This is a
working class city with a strong political left wing. Like half the people
living here are either immigrants themselves or have at least one immigrant
parent too. Demographically, Malmö just doesn’t have the critical mass of
obedient white people necessary to successfully artwash the ethnic cleansing of
Gaza.</p>

<p>The conflict between these opposing forces is intense. Projecting a 20th century
and painstakingly white vision of Swedishness onto a place like Malmö at a time
like this takes more than a couple of project managers and a marketing team.</p>

<figure>
  <img src="/2024/05/12/IMG_0157@1024x1024.webp" alt="Evening scene featuring a large black armored truck parked in a residential area." />
  <figcaption>
    Paramilitary hardware deployed by Swedish police outside Folkets Park. Photo from <a href="https://www.instagram.com/p/C6riMQNixQR/?img_index=1">Emma-Lina Johansson's Instagram</a>.
  </figcaption>
</figure>

<p>Folkets Park has a special personal significance to me. The first time I visited
Malmö, my wife was hoping to convince me to move here together. We spent a
magical day riding around the city on bikes and hanging out. It also happened to
be Eid, and when we came to Folkets Park there was a huge Eid festival there.
I’d never experienced anything like that before, and something clicked for me
about Malmö in that moment. There’s still segregation here, but less than other
places I’ve lived. I remember that day every time I visit Folkets Park.</p>

<p>For Eurovision, they’ve designated Folkets Park as the venue for the fan
village. This seems to have required fortifying the place like the compound in
Mad Max 2. There’s vehicle barriers, snipers, cops, private security, SWAT,
armored vehicles, automatic rifles… you name it, it’s probably deployed
somewhere in Folkets Park this week. I live a good couple of kilometres from the
place and even I’m tired of the sound of the police helicopters constantly
circling above it.</p>

<p>So it’s a <em>bad</em> fucking vibe down there this week. Folkets Park is part of the
soul of the city, and they’ve done it up as some kind of macabre
apartheid-themed escape room. Everyone has an anecdote about the place, like
<a href="https://www.instagram.com/p/C6mCVGWiMol/">the mother who got dragged out for trying to bring in sidewalk crayons for her kids</a>
for example. Malmö city council has been
<a href="https://www.instagram.com/p/C6mYb9wimNA/?img_index=1">working to silence dissent for weeks</a>,
culminating in the
<a href="https://www.svt.se/nyheter/lokalt/skane/har-rensar-malmo-bort-politisk-graffiti-infor-eurovision-olycklig-miss">censorship of a pro-Gaza mural on the graffiti walls outside Folkets Park</a>.
Their claim to have removed it by accident is somewhat undermined by their
hundreds of other acts of censorship over the past few weeks.</p>

<figure>
  <img src="/2024/05/12/IMG_0136@1024x768.webp" alt="Armed police guarding a clean up crew steam cleaning the area around a graffiti mural featuring a Eurovision logo and a Palestine flag." />
  <figcaption>
  </figcaption>
</figure>

<p>Despite how tense and unpleasant things are in Malmö this week, I’ve never been
more proud to live here. The local Left Party bought in about 600 Palestine
flags and had sold out the entire stock by the time I made it over there to get
mine. They’re everwhere, especially in Möllevången.</p>

<p>The protests against the ethnic cleaning of Gaza has been non-stop here for
months, and it’s only intensified with the arrival of Eurovision. The smallness
of the town helps a lot too, I think. The first protest I went to, instead of
social media, it was the low-flying police helicopters that notified me about it
and showed me how to get there.</p>

<p>Today, we’re off to what I think will be the <em>real</em> Eurovision this year, taking
place at the culture stage at Mölleplatsen this evening.</p>

<figure>
  <img src="/2024/05/12/si@1440x1440.webp" alt="" />
  <figcaption>
    <a href="https://www.instagram.com/p/C6j115Qi1Rv/?img_index=2">@stoppa.israel</a>
  </figcaption>
</figure>

<p>Liverpool hosted Eurovision last year, because Ukraine couldn’t host it due to
the war. Malmö’s sending a much more profound and grassroots message of
solidarity and peace this year. I know which one I prefer.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[My perspective on Eurovision 2024 as a resident of Malmö]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/05/12/IMG_0137@1024x1024.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/05/12/IMG_0137@1024x1024.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Can’t Buy Me Love</title>
      <link href="https://henry.catalinismith.se/2024/05/11/cant-buy-me-love/" rel="alternate" type="text/html" title="Can’t Buy Me Love" />
      <published>2024-05-11T00:00:00+02:00</published>
      <updated>2024-05-11T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2024/05/11/cant-buy-me-love</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/05/11/cant-buy-me-love/"><![CDATA[<p>It’s tax declaration season in Sweden, and I found out I owe an additional
15000kr of income tax for 2023. Ouch! As far as I can tell, it’s a side effect
of how quickly I found work after the redundancy last year. I managed to start a
new job while still in the old one’s notice period, which meant I got two
salaries for a little while, and I think that screwed up the preliminary tax
deductions.</p>

<p>It got me thinking about Sweden and the UK again. The experience of paying tax
in Sweden is really different than back home. Back home it was always this
annoying thing you didn’t really want to do. The British mindset on this issue
is best summarised as
“<a href="https://www.youtube.com/watch?v=4X48mxMPm9Q">You’ve got to pick a pocket or two</a>”.
And I dunno what the hell they’re spending the money on over there, but it’s
sure as hell not on parental pay or the posh kind of school buildings that don’t
<a href="https://www.theguardian.com/education/2023/aug/31/what-is-raac-reinforced-autoclaved-aerated-concrete-schools-buildings-england-close">randomly collapse</a>.</p>

<p>Here in Sweden there’s this cool idea that you actually get something for the
money. My childcare costs here are a fraction what they’d be back home. I’ll
notice a pothole in a road here and then a month later it might be fixed. On top
of that, there’s this very Swedish concept that
<a href="https://www.youtube.com/watch?v=l_YMrHssFXo">paying taxes is an expression of patriotism</a>.
I think this is really bloody clever. It’s the first time the concept of
patriotism even made sense to me as a force for good.</p>

<p>There’s no doubt in my mind that I could get rich a lot easier in the UK. But
every single day in Sweden my kids travel from their rent-controlled apartment
to their state-funded preschool on protected, separated cycle lanes, and there’s
no amount of individual wealth that can recreate that lifestyle in the UK.
Individualism can work miracles for the lucky few, but it’s terrible at building
whole societies.</p>

<p>It took me a few months of instalments to pay in the full 15000kr to my tax
account. By coincidence, once it was done, the bosses over at Spotify
<a href="https://www.svd.se/a/1MnOKe/spotifys-oro-hog-skatt-och-bristande-skolor">renewed their political campaign to cut taxes and deregulate the housing market</a>
in order to attract more software engineers like me to Sweden. They’ve been
<a href="https://medium.com/@SpotifySE/vi-m%C3%A5ste-agera-eller-bli-omsprungna-383bb0b808eb">pushing this since the day before I moved to Sweden</a>,
and they’ve chosen this moment to revive the campaign.</p>

<p>This came as a surprise on multiple levels. Obviously, a company whose headcount
has been shrinking globally since 2022 isn’t really arguing from a position of
strength whenever they talk about competition for talent in <em>any</em> country. But
more than that, it’s an argument that contradicts the lived experience of many
of us who’ve made the move to Sweden in particular.</p>

<p>When I accepted my Spotify offer, back in 2016, I had options. One of them was a
startup in Barcelona. So I could have picked a lower capital gains tax and a
deregulated rental market if I wanted, but I didn’t. I knew about these
differences between the two countries, and I chose Sweden. Barcelona’s
introduced rent control legislation since then too, by the way.</p>

<p>It’s tempting to try to rationalise this somehow. Like sure, they hired <em>me</em>,
but maybe they wanted to hire even <em>more</em> people? Thing is, the 2016 hypergrowth
phase that I joined during suggests otherwise. Every day, you’d try to ask
someone for directions to a meeting room and then find out they were even newer
and more lost than you were. And more recently, the rationale for the layoffs
was that the company had <em>overhired</em> during the pandemic. Spotify’s bosses have
been
<a href="https://medium.com/@SpotifySE/vi-m%C3%A5ste-agera-eller-bli-omsprungna-383bb0b808eb">doomsaying about Sweden’s tax policy for the better part of a decade</a>
now, and the claims are simply not borne out by the results so far.</p>

<p>So what’s going on here? Personally, I think that once you get above a certain
level of individual wealth and power, everything starts to look like business to
you. It’s your
<a href="https://sourcemaking.com/antipatterns/golden-hammer">golden hammer</a> after all,
and it’s given you everything you ever dreamed of, so why shouldn’t it be able
to fix society and save the planet too? Spotify’s repeated attempts to justify a
night work ban exemption on the basis of being “socially important” further
suggests how grandiose the self-image is in the upper echelons.</p>

<p>My perspective is very different. I like business plenty. I daydream often of
starting a company of my own when the kids are a little older. But I don’t see
that we need to elevate the practice of business above all other fields of human
endeavor, as if Spotify’s ability to poach a handful of machine learning
engineers from Google is worth cutting jobs in healthcare and teaching across
Sweden. And I think we should be especially cautious about the motives behind
these kinds of statements from people whose personal fortunes would benefit from
that kind of social change.</p>

<p>But these are powerful people. They have powerful friends too. Martin Lorentzon
was even invited to the prime minister’s birthday party this year. So they can
do a lot of damage in pursuit of their next big
<a href="https://www.di.se/digital/spotify-topparna-saljer-aktier-for-miljoner/">stock market payout</a>.
And as long as Microsoft Excel shows them a green number instead of a red one,
they might not even notice the negative impact on society at large.</p>

<p>Personally, I’m done simply shaking my fist at all this. The day after the 2022
elections, I made a decision to put all that energy into action instead. And one
of the most impactful types of action we can engage in as software engineers is
in creating the organising tools with which to build a bigger mass movement. In
Sweden, <a href="https://zetkin.org/">Zetkin Foundation</a> builds just that kind of
tooling for the international left.</p>

<p>In September, Zetkin’s hosting an
<a href="https://www.facebook.com/events/1123080838945326">International Code Camp</a>. If
you can fund your own travel to Malmö, they’ll provide your accommodation and
food, and together we can put our skills to work implementing our own vision for
the future of society. Zetkin’s a special thing, and I’d love to share it with
more of the people I know. So have a think about coming down here on
<a href="https://www.facebook.com/events/1123080838945326">September 27th</a>.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="zetkin" />
      

      
      
        <summary type="html"><![CDATA[It’s tax declaration season in Sweden, and I found out I owe an additional 15000kr of income tax for 2023. Ouch! As far as I can tell, it’s a side effect of how quickly I found work after the redundancy last year. I managed to start a new job while still in the old one’s notice period, which meant I got two salaries for a little while, and I think that screwed up the preliminary tax deductions.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">10 Years Of Global Thermonuclear War</title>
      <link href="https://henry.catalinismith.se/2024/03/26/ten-years-of-global-thermonuclear-war/" rel="alternate" type="text/html" title="10 Years Of Global Thermonuclear War" />
      <published>2024-03-26T00:00:00+01:00</published>
      <updated>2024-03-26T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/03/26/ten-years-of-global-thermonuclear-war</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/03/26/ten-years-of-global-thermonuclear-war/"><![CDATA[<p>It’s coming up on the tenth anniversary of the release of my infamous global
thermonuclear war game. It’s been offline for longer than it was ever online
now. I want to get the story of this thing and its general context in my life &amp;
career written down and posted in time for the ten year milestone, so here it
is.</p>

<h2 id="origins">Origins</h2>

<p>I don’t really remember how I ended up making this. My best guess is that I was
probably pissing around with the Google Maps API one day and noticed it was
possible to animate the
<a href="https://developers.google.com/maps/documentation/javascript/examples/geometry-headings">geodesic lines</a>
to look like live-updating nuclear warhead trajectories.</p>

<p>It was a challenging idea to turn into a game and I’d bought some whiteboards to
help with things like that, so I did
<a href="https://henry.catalinismith.com/2014/08/04/not-planning-any-nuclear-attacks/">some sketches</a>
to help myself work through some of the concepts and vocabulary.</p>

<h2 id="the-landlord-visit">The Landlord Visit</h2>

<p>The visit was a scheduled inspection. In the UK these are a common practice in
the rental market. In Sweden they’re not. Moving here changed my perspective on
this aspect of the story very significantly. In the UK, people’s reaction to
this story was always “I can’t believe they actually called the police”. In
Sweden, it’s “Wait, what do you mean ‘inspection’?”.</p>

<p>In one of my interviews last year I talked a lot of shit about Thatcher, and how
eye-opening it was to move to Sweden and realise what a dump the UK was in so
many ways. This was one of the many realisations on that journey.</p>

<p>There’s a lot of bloodthirst about deregulating the Swedish housing market these
days. Here’s hoping we manage to hang on to this one small dignity at least. We
have a right to enjoy our lives without the surveillance of our private homes by
the landlord class. Let’s keep that.</p>

<h2 id="the-police-report">The Police Report</h2>

<p>The whole point of publishing the blog posts about the incident was to try to
pre-empt a police intervention. I don’t know if the blog posts going viral made
any difference, but the police never came.</p>

<p>In hindsight, my fear response to the police report makes a lot of sense. The
vast majority of my police interactions back home were negative. One time, I was
walking to my local supermarket in Liverpool, and I was stopped and searched by
police for no reason, under
<a href="https://www.lancashire.police.uk/help-advice/stop-and-search/section-60-without-suspicion-searches/">section 60 of the Criminal Justice and Public Order Act 1994</a>.</p>

<p>That’s another thing we don’t have here in Sweden. Or didn’t have, more like.
Starting Thursday, Swedish police will have
<a href="https://sverigesradio.se/artikel/regeringen-visitationszoner-infors-i-mars">comparable powers for suspicionless stop-and-search</a>.
In
<a href="https://www.arbetsvarlden.se/the-cba-struggle-at-spotify-traumatized-him/">an Arbetsvärlden interview I did recently</a>,
I talked about Sweden becoming more like the UK. This would be a specific
example of that.</p>

<h2 id="the-media-attention">The Media Attention</h2>

<p>Someone on Twitter saw my blog posts and posted them on Hacker News. Someone at
The Guardian saw the Hacker News post and contacted me for quotes for
<a href="https://www.theguardian.com/technology/2014/aug/26/police-video-game-developer-global-thermonuclear-war-plans">a story about it</a>.
Various publications picked up the story from there.</p>

<p>Gaming-related stories sometimes get reinterpreted with a particular agenda
where it’s about how the stupid “normies” don’t understand the refined culture
of the poor beleaguered gamer. I saw this happen a little bit with my story.
Simultaneously, I saw it happen with Anita Sarkeesian.</p>

<p>The idea of benefiting from the same destructive social forces that I could see
destroying other people’s lives made me a little bit sick. I think I wrote
<a href="/2014/12/26/gamings-persecution-complex/">Gaming’s Persecution Complex</a> more or
less just to ease my own conscience about that.</p>

<h2 id="the-big-launch">The Big Launch</h2>

<p>The guy at The Guardian told me what day the story would go up, and that became
my deadline. It was fun to have an externally imposed deadline. It helped me to
cut my crap and ship the thing. The whole project had been stuck in a
<a href="https://codeberg.org/henrycatalinismith/wargames/commit/91ba8000dd3bf120a477eb6ffdd3e6999f4f7299">boring dead zone of aimless pissing about</a>
before that.</p>

<p>Once the story was up and the game was online it was really exciting. I kept
having to do all these seat-of-my-pants optimisations to keep everything
running. The back end leaked memory and crashed from time to time. The front end
slowed to a crawl with all the missiles and I had to do things like
<a href="https://codeberg.org/henrycatalinismith/wargames/commit/98f74759dad114ec3e68adabe09ad127e1f3145f">limit how many would even render</a>.</p>

<p><img src="/2024/03/26/map@672x350.webp" alt="Google Maps screenshot covered in red dots and red geodesic lines" /></p>

<p>When I shipped this game, the Google Maps API was free. Today, serving the
amount of traffic I did back then would have cost me hundreds of dollars in Maps
API fees. I don’t know if I could have afforded it.</p>

<h2 id="the-long-tail">The Long Tail</h2>

<p>The publicity lasted about a day, and the social media buzz about a week. The
game itself retained users way better than it had any right to though. People
kept coming back, and the thing stayed decently busy for a long while after the
launch.</p>

<p>The traffic did eventually flatline. But then, something surprising happened:
every so often the server would light up again with a surge of activity for a
short while. Individual online communities would rediscover it and have a few
days of fun playing it together before moving on.</p>

<p>After a while, it narrowed down to one community in particular who came back
repeatedly. And unfortunately, that community was creeps from 4chan. I managed
to track down the thread about it once based on the top referers list in Google
Analytics, and they were all in there hooting and hollering about role playing
their violent racist fantasies. I stopped renewing the domain name after that,
and the game’s been offline ever since.</p>

<h2 id="career-impact">Career Impact</h2>

<p>It seemed like everyone in the Bristol tech scene in the mid 2010s had heard of
the nuclear landlord police report story. It was a pretty good ice breaker at
events for a while.</p>

<p>Attributing career progression to any individual piece of work is tricky. But I
did move jobs not long after all this. And I think having such a well-known
JavaScript side project helped a lot, because the new gig was my first ever
JavaScript job, after a very PHP-centric career up to that point.</p>

<p>That job also happened to be MixRadio, which led me directly to Spotify when it
later closed down. So I do reckon Global Thermonuclear War helped me escape the
UK. Maybe not directly. But I doubt I’d be sat here in Malmö today writing this
otherwise.</p>

<h2 id="the-webgl-demo-thing">The WebGL Demo Thing</h2>

<p>It never sat well with me having such an important career milestone project just
be this offline app from the past. I wanted there to at least be <em>something</em>
online to show people. Eventually the idea came to me that I could probably make
some kind of watchable demo out of some of the old missile launch log data.</p>

<p>So now <a href="/2020/11/19/wargames/">there’s that</a>.</p>

<figure>
  <video controls="" src="/2024/03/26/wargames@688x570.mp4" poster="/2024/03/26/wargames@688x570.webp" preload="none"></video>
  <figcaption>
    <a href="https://henry.catalinismith.se/2020/11/19/wargames/">
      henry.catalinismith.se/2020/11/19/wargames/
    </a>
  </figcaption>
</figure>

<p>It’ll do! It gives a nice sense of the scale of the popularity the game reached
at its peak, and it preserves the sense of endlessness and pointlessness to the
conflict. It was fun as hell to build, too. And it means there’s <em>something</em>,
not just an old readme.</p>

<h2 id="krisen-eller-kriget">Krisen Eller Kriget</h2>

<p>Being in the news as a suspected nuclear terrorist leaves you with a lingering
interest in the general topic. In 2018, the Swedish government sent everyone
these little booklets about what to do if there’s a war. I was a little bit
obsessed with these. In them, they mention public fallout shelters.</p>

<p><img lang="sv" alt="Skyddsrum och andra skyddande utrymmen. Skyddsrum ska ge skydd ät befolkningen i krig. Alla skyddsrum och byggnader med skyddsrum är märkta med skylt. Du tillhör inget särskilt skyddsrum utan anänder det som finns närmast. Ta reda pä var ditt närmaste skyddsrum finns där du bor och där du befinner dig dagtid. Vid flyglarm ska du omedelbart bege dig till ett skyddsrum eller i nödfall till annat skyddande utrymme som källare, tunnel eller tunnelbanestation." src="/2024/03/26/skyddsrum@672x829.webp" /></p>

<p>Sweden has
<a href="https://www.thelocal.se/20171101/why-sweden-is-home-to-65000-fallout-shelters">65000 of these fallout shelters</a>.
Within a one block radius of my home, there are four. Within a one kilometre
radius, there are about 100. Remember what I said about life in Sweden making me
realise what a dump the UK was? Yeah, they don’t have anything like this back
home. What kind of dickhead country builds one of the world’s largest nuclear
arsenals but nowhere to shelter from its effects?</p>

<p>Anyway, I read a really captivating alternate history timeline last year called
<a href="https://www.alternatehistory.com/wiki/doku.php?id=timelines:protect_and_survive">Protect &amp; Survive</a>.
It’s about the UK in the aftermath of a global thermonuclear war in 1984, and
it’s fucking horrible. Towards the end, the few survivors establish radio
contact with Sweden, who are doing comparatively well, I guess partly due to the
neutrality making us less of a target, and partly due to all these public
fallout shelters.</p>

<p>Been reflecting on that aspect of the story a fair bit recently. Sweden’s NATO
membership got approved two weeks ago, and I found myself living in an EU &amp; NATO
member state again, just like back in 2014. And I guess the fallout shelters
here are more directly valuable to me personally than the UK’s nuclear warheads
ever were, so that’s a solid upgrade. But wasn’t the only winning move not to
play? Definitely was in my version of the game at least.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="gamedev" />
      
        <category term="wargames" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[It’s coming up on the tenth anniversary of the release of my infamous global thermonuclear war game. It’s been offline for longer than it was ever online now. I want to get the story of this thing and its general context in my life &amp; career written down and posted in time for the ten year milestone, so here it is.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/03/26/wargames@688x570.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/03/26/wargames@688x570.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Tailbone: Disk 2</title>
      <link href="https://henry.catalinismith.se/2024/03/23/tailbone-disk-2/" rel="alternate" type="text/html" title="Tailbone: Disk 2" />
      <published>2024-03-23T00:00:00+01:00</published>
      <updated>2024-03-23T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/03/23/tailbone-disk-2</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/03/23/tailbone-disk-2/"><![CDATA[<video controls="" preload="none" poster="/2024/03/23/tailbone-disk-2@512x512.webp">
  <source src="/2024/03/23/tailbone-disk-2@512x512.mp4" type="video/mp4" />
</video>

<p><a href="https://henry.catalinismith.com/tailbone/disk2">Tailbone: Disk 2</a> is a harder
version of the <a href="https://henry.catalinismith.com/tailbone/">original Tailbone</a>.
In the original version, the pace starts off nice and easy-going, and the
difficulty only ramps up properly after you complete the first 10 – 15 combos.
In Disk 2 the difficulty is basically maxed out immediately.</p>

<p>I made this for myself, because I got bored of playing the easy parts. The
harder levels are the stuff I think is the most fun to play in Tailbone, and I
wanted a way to just skip straight to that. I don’t actually know if anyone else
will enjoy this or even find it playable at all. It’s definitely not going to be
a good way to
<a href="/2022/10/25/tailbone-instructions/">learn how to play Tailbone from scratch</a>.</p>

<p><a href="https://henry.catalinismith.com/tailbone/disk2">Give it a try</a> anyway if you’re
curious!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="gamedev" />
      
        <category term="tailbone" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/03/23/tailbone-disk-2@512x512.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/03/23/tailbone-disk-2@512x512.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Stabilizing Zetkin’s Playwright Tests</title>
      <link href="https://henry.catalinismith.se/2024/03/17/stabilizing-zetkins-playwright-tests/" rel="alternate" type="text/html" title="Stabilizing Zetkin’s Playwright Tests" />
      <published>2024-03-17T00:00:00+01:00</published>
      <updated>2024-03-17T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/03/17/stabilizing-zetkins-playwright-tests</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/03/17/stabilizing-zetkins-playwright-tests/"><![CDATA[<p>I recently did
<a href="https://github.com/zetkin/app.zetkin.org/pull/1807">some work addressing the flakiness of some end-to-end playwright tests</a>
for Zetkin Foundation. This class of reliability issue is something I’ve seen
affect almost every team I’ve worked with throughout my career. It’s hard, but
for once I think we actually secured a win with this PR. It’s a bloody <em>messy</em>
PR though. So let’s zoom in on what we actually changed and why it helped.</p>

<h2 id="works-on-my-machine">Works On My Machine</h2>

<p>A typical playwright test for a web app feature involves a series of button
clicks leading to an assertion at the end. It’s common for each UI event to
depend on the ones before it. For example, a click on a menu item depends on the
previous click which opens the menu. We can distil this down to an example like
the one below, in which we click three buttons in turn and then see a status
message.</p>

<p><img src="/2024/03/17/fast@1200x256.gif" alt="Animation in which a button labeled &quot;one&quot; is clicked, which reveals a button labeled &quot;two&quot;. When &quot;two&quot; is clicked it reveals button &quot;three&quot;. When &quot;three&quot; is clicked a message appears that reads &quot;success&quot;. In each case, the effect of the button presses is immediate." /></p>

<p>If you’ve done any end-to-end testing before, you can probably imagine how you’d
automate a test for that. You’d have three automated clicks and then at the end
you’d assert the presence of the <code class="language-plaintext highlighter-rouge">success</code> text.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nf">click</span><span class="p">(</span><span class="dl">"</span><span class="s2">text=one</span><span class="dl">"</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nf">click</span><span class="p">(</span><span class="dl">"</span><span class="s2">text=two</span><span class="dl">"</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nf">click</span><span class="p">(</span><span class="dl">"</span><span class="s2">text=three</span><span class="dl">"</span><span class="p">);</span>
<span class="nf">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nf">locator</span><span class="p">(</span><span class="dl">"</span><span class="s2">text=success</span><span class="dl">"</span><span class="p">).</span><span class="nf">isVisible</span><span class="p">()).</span><span class="nf">toBeTruthy</span><span class="p">();</span>
</code></pre></div></div>

<p>This would be a perfectly normal thing to write, and you’d see it working just
fine when you tested it locally too. So you’d commit, push and it’d probably get
merged without comment as well.</p>

<h2 id="results-may-vary">Results May Vary</h2>

<p>Developer laptops tend to have fairly stable performance, whereas CI
environments are under much heavier and more variable system load. Let’s take
our example page from before and simulate running it under shitty conditions
where each click takes an entire second to process.</p>

<p><img src="/2024/03/17/slow@1200x256.gif" alt="Animation in which a button labeled &quot;one&quot; is clicked, which reveals a button labeled &quot;two&quot;. When &quot;two&quot; is clicked it reveals button &quot;three&quot;. When &quot;three&quot; is clicked a message appears that reads &quot;success&quot;. In each case, the button presses take one second to process." /></p>

<p>The test fails when things slow down this much. Playwright tries to find an
element matching the <code class="language-plaintext highlighter-rouge">text=success</code> selector at the end and gives up when it
doesn’t find it immediately after clicking on button three.</p>

<p>That might happen to a random end-to-end test in a CI build for an unrelated
code change you’re trying to merge. That’s when you say “God damn these stupid
fucking flaky tests” and you retrigger the build, hoping for a better outcome
next time. And if you get a green build on the retry, it’s probably because
system load is lower on the cloud instance your second attempt runs on.</p>

<h2 id="wait-for-it">Wait For It</h2>

<p>The trick is in replacing that final <code class="language-plaintext highlighter-rouge">isVisible()</code> assertion with something that
doesn’t fail if the <code class="language-plaintext highlighter-rouge">success</code> text takes a moment to appear. Here’s an example
using Playwright’s
<a href="https://playwright.dev/docs/api/class-locator#locator-wait-for"><code class="language-plaintext highlighter-rouge">waitFor</code></a>
method.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">status</span> <span class="o">=</span> <span class="nx">page</span><span class="p">.</span><span class="nf">locator</span><span class="p">(</span><span class="dl">"</span><span class="s2">#status</span><span class="dl">"</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">status</span><span class="p">.</span><span class="nf">waitFor</span><span class="p">({</span> <span class="na">state</span><span class="p">:</span> <span class="dl">"</span><span class="s2">visible</span><span class="dl">"</span> <span class="p">});</span>
<span class="nf">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">status</span><span class="p">.</span><span class="nf">textContent</span><span class="p">()).</span><span class="nf">toBe</span><span class="p">(</span><span class="dl">"</span><span class="s2">success</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div>

<p>The first line there doesn’t do much. It just instantiates a
<a href="https://playwright.dev/docs/api/class-locator">locator</a> object for the status
text. The second line tells Playwright to chill for a second until the status
appears. Then finally, line three asserts that its text content is <code class="language-plaintext highlighter-rouge">success</code>.</p>

<p>Whereas the code in the original example will randomly fail depending on system
load at runtime, this approach is robust enough to pass reliably even if it’s
running on a struggling, overworked CI server.</p>

<h2 id="locators-for-everything">Locators For Everything</h2>

<p>It goes beyond just the assertion at the end, actually. The first thing
<a href="https://playwright.dev/docs/api/class-page#page-click">Playwright’s documentation for <code class="language-plaintext highlighter-rouge">page.click()</code></a>
says about it is not to use it.</p>

<p><img src="/2024/03/17/click@1198x378.webp" alt="Discouraged. Use locator-based locator.click() instead. Read more about locators." /></p>

<p><a href="https://playwright.dev/docs/locators">Playwright’s docs for locators</a> describe
them as “the central piece of Playwright’s auto-waiting and retry-ability”.
Nice! Sounds like a great all-purpose flakiness vaccine to me.</p>

<p>For our example test, that means that it ends up looking like this.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const one = page.locator("text=one");
const two = page.locator("text=two");
const three = page.locator("text=three");
const status = page.locator("#status");

await one.click();
await two.click();
await three.click();

await status.waitFor({ state: "visible" });
expect(await status.textContent()).toBe("success");
</code></pre></div></div>

<p>That’s about as good as I understand what I did in
<a href="https://github.com/zetkin/app.zetkin.org/pull/1807">the Zetkin PR</a>. I always
resort to a bit of guesswork and superstitious coding when dealing with tricky
stuff like this, and this time was no different. We managed to catch and remove
<a href="https://github.com/zetkin/app.zetkin.org/pull/1807#discussion_r1503611724">some of it</a>
but who knows if that was all of it.</p>

<p>Was every <code class="language-plaintext highlighter-rouge">waitFor()</code> call I added 100% necessary? Honestly not sure. I sure as
hell didn’t replace all the <code class="language-plaintext highlighter-rouge">page.click()</code> calls with locators either. Maybe I
should have? IDK, it’s tricky cos that would have <em>felt</em> superstitious unless I
could reliably reproduce a test failure that was fixed by swapping to locators,
but OTOH the very nature of this problem is that you can’t reliably reproduce
them. This stuff is bloody hard, but at least we scored ourselves a clear win
here.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="zetkin" />
      

      
      
        <summary type="html"><![CDATA[Reducing end-to-end test flakiness in a real-world open source app.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/03/17/fast@1200x256.gif" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/03/17/fast@1200x256.gif" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Fixing 666a (again)</title>
      <link href="https://henry.catalinismith.se/2024/03/09/fixing-666a-again/" rel="alternate" type="text/html" title="Fixing 666a (again)" />
      <published>2024-03-09T00:00:00+01:00</published>
      <updated>2024-03-09T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/03/09/fixing-666a-again</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/03/09/fixing-666a-again/"><![CDATA[<p>So apparently <a href="https://666a.se/">666a</a> is still capable of surprising me. A few
months ago I wrote about the work I’d done to address an issue I’ve been calling
“<a href="/2024/01/09/operating-666a/">document lag</a>”. Since then, I’ve continued to
keep a constant eye on the system’s reliability. This week, something new
cropped up.</p>

<p>The investigation began when I did my usual round of manual checks of
Arbetsmiljöverket’s public archive, comparing the canonical data there to the
notifications I’ve received recently from 666a. There was a document listed that
I hadn’t received an email about, which is a worst case scenario for me. The
whole point of the service is for that not to happen, so it’s my most feared
type of bug and basically a priority zero issue. Now that it’s fixed, I wanted
to write up what was wrong and how I resolved it.</p>

<h2 id="the-old-system">The old system</h2>

<p>At the core of 666a was this one cron job called the “day job”. When it started,
the first thing it did was to check how many documents it saw last time it ran.
Then it’d add one to that number and start looking for more.</p>

<pre><code class="language-mermaid">graph TD;
A[DayJob] --&gt;|Start| B(n = how many documents we've previously found for this day);
B --&gt; C(Try to fetch more documents starting at n + 1);
C --&gt; D{Were there any?}
D --&gt;|Yes| B;
D --&gt;|No| E[Done];
</code></pre>

<p>This approach depended on the assumption that new documents would always appear
at the end. I’d had my suspicions about this assumption, but it had always help
up. Until last week anyway.</p>

<h2 id="diagnosis">Diagnosis</h2>

<p>Sometime around Tuesday I noticed
<a href="https://www.av.se/om-oss/sok-i-arbetsmiljoverkets-diarium/?id=2024/006453-5">a document</a>
had been filed backdated to last Friday but never got picked up by 666a. When I
checked the data for Friday, I found the old “Day Job” was up to
<a href="https://www.av.se/om-oss/sok-i-arbetsmiljoverkets-diarium/?page=95&amp;sortDirection=Desc&amp;sortOrder=Dokumentdatum&amp;OnlyActive=False&amp;FromDate=2024-03-01&amp;ToDate=2024-03-01&amp;ShowToolbar=True">page 95 of the results</a>.</p>

<p>Thing is, the missing document wasn’t on page 95. It wasn’t on any of the pages
after it either. I took a deep breath and began clicking back through the
previous pages one by one. Eventually I came to
<a href="https://www.av.se/om-oss/sok-i-arbetsmiljoverkets-diarium/?page=95&amp;sortDirection=Desc&amp;sortOrder=Dokumentdatum&amp;OnlyActive=False&amp;FromDate=2024-03-01&amp;ToDate=2024-03-01&amp;ShowToolbar=True">page 84</a>,
and there it was.</p>

<p><img src="/2024/03/09/diarium-p84@1024x1024.webp" alt="Screenshot of the Work Environment Authority's search tool showing results corresponding to the page 84 URL linked earlier." /></p>

<h2 id="implications">Implications</h2>

<p>This design flaw meant that 666a would miss something in the region of 5% of all
documents filed. That’s a bad enough number that there’s no ifs or buts about
whether it needed fixing or how soon. This was a “fix-it-today” job.</p>

<p>Fix-it-today jobs are tricky these days. We’ve got a lot going on at home. I
have about 30 minutes tops in a given day for something like this. So not only
did I need an immediate fix for a design-level defect, but also it needed to be
a small code change that would be quick to implement and YOLO into production.</p>

<h2 id="the-fix">The fix</h2>

<p>By the time the kids were in bed the quick fix had occurred to me: empty the
search result cache for each day before updating it. This leads to the exact
same algorithm as before, except with a “delete” step added at the very
beginning.</p>

<pre><code class="language-mermaid">graph TD;
A[DayJob] --&gt;|Start| F(Clear cache for this day)
F --&gt; B(n = how many documents we've previously found for this day);
B --&gt; C(Try to fetch more documents starting at n + 1);
C --&gt; D{Were there any?}
D --&gt;|Yes| B;
D --&gt;|No| E[Done];
</code></pre>

<p>That forces the job to re-fetch everything from scratch, beginning at page 1. It
means nothing gets missed. And importantly, it also made the fix easy to
implement. In fact, it was damn near a one line fix.</p>

<p><img src="/2024/03/09/fix@1220x600.webp" alt="Version control diff showing the addition of a line that reads “day.searches.destroy_all if options[:purge]” in a file called “app/jobs/work_environment/day_job.rb”" /></p>

<h2 id="scaling-it-up">Scaling it up</h2>

<p>The downside of this fix is that dramatically increases the number of HTTP
requests necessary to check if new documents have been filed. My informal budget
I set for myself is that I try to keep the amount of requests I send to <code class="language-plaintext highlighter-rouge">av.se</code>
within the region of 1000 per day so that I’m as good as unnoticeable in terms
of the load I put on their service.</p>

<p>As part of my
<a href="/2024/01/09/operating-666a/">previous fixes to account for backdated filings</a>
I’d programmed 666a to check the past 64 days every day. Checking all 64 on a
cold cache would blow that informal 1000 requests budget. I’m really happy with
the compromise I came up with here. Instead of checking every day sequentially,
like <code class="language-plaintext highlighter-rouge">1.days.ago</code>, <code class="language-plaintext highlighter-rouge">2.days.ago</code>, <code class="language-plaintext highlighter-rouge">3.days.ago</code>, <code class="language-plaintext highlighter-rouge">4.days.ago</code>, <code class="language-plaintext highlighter-rouge">5.days.ago</code> and so
on up to 64, I switched to powers of two. So instead I’m checking <code class="language-plaintext highlighter-rouge">1.days.ago</code>,
<code class="language-plaintext highlighter-rouge">2.days.ago</code>, <code class="language-plaintext highlighter-rouge">4.days.ago</code>, <code class="language-plaintext highlighter-rouge">8.days.ago</code> and so on.</p>

<p>In practice this won’t lead to any significant notification delays, because the
vast majority of documents are listed the day after their filing date anyway.
And the decrease in days checked compensates <em>perfectly</em> for the increase in
number of requests necessary to check a day. I actually keep statistics about
how many requests to <code class="language-plaintext highlighter-rouge">av.se</code> I make per day, and the chart really drives home
how steady the numbers stay despite the change.</p>

<p><img src="/2024/03/09/rpd@1896x708.webp" alt="Bar chart with a y axis ranging from 0 to 1500 and an x axis ranging from 2023-10-30 to the present day. The bars are typically between 700 and 1200 high. Near the end, a pink arrow and text annotates one of the bars as “First day with new algorithm”." /></p>

<h2 id="whats-next">What’s next?</h2>

<p>It’s great fun having this kind of side project to look after. This one in
particular feels like maintaining an old motorbike or something. A Rails app
with a cron job that sends email notifications is kind of a retro combination at
this point. And it’s running beautifully now. Dead happy with it.</p>

<p>By now, 666a has processed over a quarter of a million documents and sent over a
thousand notification emails. It’s crystal clear the demand exists for the
service. The daily email volume still fits within SendGrid’s free tier, but it’s
growing steadily enough that I’ve already taken the time to get pre-approved for
a paid subscription when the time comes.</p>

<p>If I’d shipped it as a “beta” – and maybe I should have – this would probably be
the moment I’d officially say it was “out of beta”. Right now my worst fear
would be for somebody at Arbetsmiljöverket to suddenly ship a redesign of the
diarium search feature. Especially if that redesign depended on JavaScript and
reworked all the logic around sorting, filtering and pagination. So let’s all
cross our fingers for that not to happen!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[So apparently 666a is still capable of surprising me. A few months ago I wrote about the work I’d done to address an issue I’ve been calling “document lag”. Since then, I’ve continued to keep a constant eye on the system’s reliability. This week, something new cropped up.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/03/09/diarium-p84@1024x1024.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/03/09/diarium-p84@1024x1024.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Atuin and chezmoi are the dog’s bollocks</title>
      <link href="https://henry.catalinismith.se/2024/02/25/atuin-and-chezmoi-are-the-dogs-bollocks/" rel="alternate" type="text/html" title="Atuin and chezmoi are the dog’s bollocks" />
      <published>2024-02-25T00:00:00+01:00</published>
      <updated>2024-02-25T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/02/25/atuin-and-chezmoi-are-the-dogs-bollocks</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/02/25/atuin-and-chezmoi-are-the-dogs-bollocks/"><![CDATA[<p>My <a href="https://codeberg.org/henrycatalinismith/dotfiles">dotfiles repo</a> is the
oldest one I still have. Its
<a href="https://codeberg.org/henrycatalinismith/dotfiles/commit/0f498b9">first commit</a>
is from 2012. The message I wrote for that commit tells a familiar story.</p>

<blockquote>
  <p>Fresh start for config files<br /> The old config repo had become bloated and
unwieldy</p>
</blockquote>

<p>I don’t remember what went wrong, but evidently, things had had gotten out of
control. Synchronising development tools across multiple computers has always
been such a pain in the ass. Not any more though. 12 years later, I finally have
a setup in place that I’m happy with.</p>

<h2 id="atuin">Atuin</h2>

<p>Losing shell history when swapping back and forth between different terminal
tabs or different computers has always been frustrating. It’s an annoyance I’d
accepted as inevitable. For no particular reason, I recently became inspired to
question that inevitability, and found <a href="https://atuin.sh/">Atuin</a> soon after.</p>

<p>No idea how I lived without this tool before. In particular, as
<a href="/2024/02/07/screencasting-secrets">someone who uses <code class="language-plaintext highlighter-rouge">ffmpeg</code> frequently</a>, it’s
been amazing to be able to refer back to any given set of filters I’ve ever
used.</p>

<p>The end-to-end encrypted sync feature is a game changer as well. Having access
to your full shell history on every computer you use feels like it changes what
the terminal <em>is</em> somehow. Brings it a bit closer to being an extension of you
or something, I don’t know.</p>

<p>The stats are cool too. For example, <code class="language-plaintext highlighter-rouge">atuin stats -c 3</code> prints your top three
most-used commands.</p>

<p><img style="object-fit: none" src="/2024/02/25/atuin-stats@672x256.webp" alt="[▮▮▮▮▮▮▮▮▮▮] 1626 git s [▮▮▮ ] 616 git b [▮▮▮ ] 604 yarn test Total commands: 10268 Unique commands: 2091" /></p>

<p>My top command there – <code class="language-plaintext highlighter-rouge">git s</code> – is my alias for <code class="language-plaintext highlighter-rouge">git status</code>. Learning that I’m
running that so often made me realise that those fancy terminal prompts that
include git status info are worth the trouble. I’d stopped bothering with them
because of the dotfiles syncing problem, and this inspired me to check in and
see if it was solved yet. It was!</p>

<h2 id="chezmoi">chezmoi</h2>

<p>There are a lot of <a href="https://dotfiles.github.io/utilities/">dotfiles tools</a>. Most
of them are effectively proof-of-concept in terms of the maturity level of the
implementation. Someone has a core idea they’re excited about, they implement
that, then the project quietly goes into maintenance mode. What makes
<a href="https://www.chezmoi.io/">chezmoi</a> the best isn’t any particular concept – it’s
the work that’s gone into polishing the annoying details around the edges.</p>

<p>In my case, I just wanted to use it to set up oh-my-zsh automatically and keep
the config in sync between different computers. There’s an
<a href="https://www.chezmoi.io/user-guide/include-files-from-elsewhere/">example in the chezmoi docs showing how to do exactly this thing</a>.</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[</span><span class="s">".oh-my-zsh"</span><span class="k">]</span>
    <span class="n">type</span> <span class="o">=</span><span class="w"> </span><span class="s">"archive"</span>
    <span class="n">url</span> <span class="o">=</span><span class="w"> </span><span class="s">"https://github.com/ohmyzsh/ohmyzsh/archive/master.tar.gz"</span>
    <span class="n">exact</span> <span class="o">=</span><span class="w"> </span><span class="kc">true</span>
    <span class="n">stripComponents</span> <span class="o">=</span><span class="w"> </span><span class="mi">1</span>
    <span class="n">refreshPeriod</span> <span class="o">=</span><span class="w"> </span><span class="s">"168h"</span>
</code></pre></div></div>

<p>I was blown away by how easy it was to set that up. Syncing a few little
personal config files has always been the easy part, and it’s generally these
externally-sourced configs where things become difficult. You always get it
syncing eventually, but it breaks along the way and so the end result feels
brittle. Somehow chezmoi has cracked it. For once this feels like it will work
on the first try when I next have to set up a new computer.</p>

<h2 id="things-are-good">Things are good</h2>

<p>I’m in a really good place at the moment when it comes to tools. Shipping 666a
with
<a href="/2024/01/14/my-sqlite-in-production-epiphany">SQLite as its production database</a>
was a huge personal revelation, going
<a href="/2024/01/30/maining-vscode-terminal">all-in on VSCode terminal</a> has decluttered
my workflow, and doing a bunch of React Native at work is scratching a really
longstanding itch. I’m getting tons done and feeling happy in the process.</p>

<p>Interestingly, the creator of Atuin has
<a href="https://ellie.wtf/posts/i-quit-my-job-to-work-full-time-on-my-open-source-project">quit her day job to found a company and work on Atuin full time</a>.
Will be fun to see what happens next there.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[My dotfiles repo is the oldest one I still have. Its first commit is from 2012. The message I wrote for that commit tells a familiar story.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/02/25/atuin-stats@672x256.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/02/25/atuin-stats@672x256.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">This Machine Kills Fascists</title>
      <link href="https://henry.catalinismith.se/2024/02/15/this-machine-kills-fascists/" rel="alternate" type="text/html" title="This Machine Kills Fascists" />
      <published>2024-02-15T00:00:00+01:00</published>
      <updated>2024-02-15T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/02/15/this-machine-kills-fascists</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/02/15/this-machine-kills-fascists/"><![CDATA[<figure>
 <img src="/2024/02/15/this-machine-kills-fascists@2048x1365.webp" alt="Laptop on table in the foreground, person in a grey hoodie typing in the background. A sticker reading 'This machine kills fascists' is visible on the laptop." />
</figure>

<p>Really cool photo from a bit of a
<a href="https://www.arbetsvarlden.se/the-cba-struggle-at-spotify-traumatized-him/">depressing Arbetsvärlden interview I did</a>.
TCO aren’t particularly radical so it’s a rare treat to get a slogan like this
into their magazine.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      
        <category term="zetkin" />
      

      
      
        <summary type="html"><![CDATA[Really cool photo from a bit of a depressing Arbetsvärlden interview I did.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/02/15/this-machine-kills-fascists@2048x1365.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/02/15/this-machine-kills-fascists@2048x1365.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Screencasting Secrets</title>
      <link href="https://henry.catalinismith.se/2024/02/07/screencasting-secrets/" rel="alternate" type="text/html" title="Screencasting Secrets" />
      <published>2024-02-07T00:00:00+01:00</published>
      <updated>2024-02-07T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/02/07/screencasting-secrets</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/02/07/screencasting-secrets/"><![CDATA[<p>Screencasting is my secret weapon as a remote software engineer. I’m one of
those people who just wants to go fast all the time, and you get to move a lot
faster once you get good at showing people what you’ve made and putting them at
ease about the idea of shipping it. At any job, one of the most common questions
I hear is “How do you do all those GIFs and videos and screenshots and stuff?”.
Here’s how.</p>

<h2 id="text-is-first">Text is first</h2>

<p>Working well with plain old text is a prerequisite for any kind of screencasting
success. Deep down it’s the same thing: you’re trying to get information from
your head into someone else’s. If your written communication isn’t clear, your
screen recordings won’t be either.</p>

<p>Think about how you report bugs, for example. The phrase “it’s broken” might
mean anything from “it’s crashing on startup for 100% of users” to “I don’t feel
like the performance is reliable enough”. The phrase “the numbers are wrong”
might mean something as severe as “these miscalculations will get us sued by the
tax authorities” or as minor as “we’re showing too many decimal places here”. If
your text communication style is vague, your screenshots and screen recordings
will probably be vague too.</p>

<p>The trick with the bug report example is to think about what your expectations
were, contrast them with what actually happened, and write something that
conveys the difference between the two as clearly as possible. It’s a thought
process that requires you to put yourself in the shoes of the audience and take
the time to shape the message around their perspective. Half the ingredients of
success when producing a video or screenshot of something is applying that same
thought process in a visual way.</p>

<h2 id="screenshots">Screenshots</h2>

<p>The simplest thing to share is often a screenshot. If something can be conveyed
in a static image, it’s almost always preferable to do so rather than deal with
the extra headaches that come with video. The principles of taking good
screenshots, such as tailoring the framing and sizing to suit the target
platform and audience, are fundamental to good screen recordings too.</p>

<p>You can take a screenshot of your entire desktop by pressing ⌘⇧3. That’s usually
the wrong option though. It’s almost always better to use ⌘⇧4 and then select a
region using the mouse. Another great option is to press ⌘⇧4, then space, and
then click on a window to take a screenshot of it. In the following video, I’ll
demo all three of these, in the same order as they’re written.</p>

<figure>
  <video src="/2024/02/07/screenshot@1024x640.mp4" poster="/2024/02/07/screenshot@1024x640.webp" controls="" preload="none"></video>
  <figcaption>
    I'm using <a href="https://appahead.studio/apps/keystroke-pro/">Keystroke Pro</a> in the video above to visualise the key presses. It's a tool I recommend very highly. It comes in especially handy if you need to screencast a series of keyboard interactions like this one.
  </figcaption>
</figure>

<p>Even the humble screenshot has some nuances that are worth thinking about. Where
will you upload your screenshot and how much space is available for it there?
What size is optimal to help your audience understand your point as easily as
possible? If there are multiple windows involved, is there some way you can
rearrange them to structure the information in the screenshot more clearly?</p>

<p>If you’re posting something on a GitHub issue or pull request, for example,
you’ll have about 800px of width to play with. Could it work for you to resize
the window in preparation for your screenshot so that it doesn’t appear too
zoomed out and squished there? For
<a href="https://github.com/zetkin/app.zetkin.org/pull/1631">before/after screenshots</a>,
it fits particularly well if both screenshots are a common mobile resolution.</p>

<h2 id="annotated-screenshots">Annotated screenshots</h2>

<p>Sometimes your screenshots need a little extra something. Like occasionally it’s
just easier to
<a href="https://github.com/zetkin/app.zetkin.org/pull/1641">visually highlight some detail</a>
in there with an annotation. For this, I use Skitch.</p>

<p>Awkwardly,
<a href="https://techcrunch.com/2011/08/18/evernote-acquires-image-sharing-site-skitch/">Evernote acquired Skitch back in 2011</a>,
and in recent years it’s basically become abandonware. But for now at least, you
can still do <code class="language-plaintext highlighter-rouge">brew install --cask skitch</code>, and it’ll run. In theory you can do
⌘⇧5 to take screenshots directly into Skitch, but in practice that’s been broken
for years, so I take them with the macOS native ⌘⇧4 and then drag &amp; drop them
into Skitch like this.</p>

<figure>
  <video src="/2024/02/07/skitch@1024x640.mp4" poster="/2024/02/07/skitch@1024x640.webp" controls="" preload="none"></video>
  <figcaption>
    I'm using the ⌘-Space keyboard shortcut here to open Skitch using <a href="https://support.apple.com/guide/mac-help/search-with-spotlight-mchlp1008/mac">Spotlight</a>.
  </figcaption>
</figure>

<p>It’s a shame Skitch is slowly dying. I’ve been recommending it to colleagues
since like 2015, and still occasionally see someone I introduced it to share
something annotated with it on social media. Will be sad when that connection is
finally broken.</p>

<h2 id="animated-gif-screen-recordings">Animated GIF screen recordings</h2>

<p>You can get a lot done with screen recordings in animated GIF format. They’re
incredibly versatile, because almost all chat &amp; collaboration platforms allow
you to post GIFs. <a href="https://gifox.app/">Gifox</a> is the best app for this, and well
worth the cost.</p>

<p>Fear of failure holds people back from trying this. People worry it’ll require
advanced technical skills. It doesn’t. Here’s a video of the full end-to-end
recording and editing workflow to produce one GIF. It takes less than a minute.</p>

<figure>
  <video src="/2024/02/07/gifox@1024x640.mp4" poster="/2024/02/07/gifox@1024x640.webp" controls="" preload="none"></video>
  <figcaption>
    This is what it looks like to create a screencast GIF using Gifox. The app I'm using as an example to record is <a href="https://github.com/IvanMathy/Boop">Boop</a>.
  </figcaption>
</figure>

<p>Gifox gives you lots of control over how to export your GIF. The one I use most
often is the resizing option, which is why I made sure to demo it in the video
above. Big animated GIFs take so long to load that nobody will watch your video,
and scaling down is an easy way to avoid this. Most of the time you don’t really
need a full 100% original size GIF either.</p>

<p>The secret to doing these things right is to keep them short. Animated GIFs
almost never have playback controls, so your audience has no way to rewind or
pause your video. And longer GIFs will exceed most platforms’ upload file size
limits anyway.</p>

<p>Another thing to bear in mind is that GIFs loop. Sometimes it’s necessary for
the clarity of the video that the end point should be very distinctive. Other
times it’s super nice to make the loop completely seamless. It’s a case-by-case
thing and you get a feel for it after a while.</p>

<h2 id="video-screen-recordings">Video screen recordings</h2>

<p>When something won’t fit within the limitations of the GIF format, I’ll fire up
QuickTime and do a screen recording instead. This has a similar workflow to
Gifox. You select an area to record, do your demo, end the recording with the
little taskbar stop button, and then press ⌘T to trim the clip.</p>

<figure>
  <video src="/2024/02/07/quicktime@1024x640.mp4" poster="/2024/02/07/quicktime@1024x640.webp" controls="" preload="none"></video>
  <figcaption>
    I made all the other videos for this post using this workflow. <em>This</em> one took some fucking doing though.
  </figcaption>
</figure>

<p>It’s worth checking the options before hitting <code class="language-plaintext highlighter-rouge">Record</code>. Pay particular
attention to the microphone setting. Most of the time you’ll want to set this to
<code class="language-plaintext highlighter-rouge">None</code>. Nobody needs to hear the background hum of your dishwasher while you
demo your new feature or whatever you’re recording.</p>

<p>QuickTime will output a fairly chunky <code class="language-plaintext highlighter-rouge">.mov</code> file at the end of this. I tend to
use <code class="language-plaintext highlighter-rouge">ffmpeg</code> to scale these down and convert them to <code class="language-plaintext highlighter-rouge">.mp4</code>. This will often cut
the file size from tens or hundreds of megabytes down into the 1MB-ish range.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg -i words.mov -vf "scale=iw/2:ih/2" words.mp4
</code></pre></div></div>

<h2 id="video-screen-recordings-with-system-audio">Video screen recordings with system audio</h2>

<p>When I need to include system audio in a screen recording, I reach for Rogue
Amoeba’s excellent <a href="https://rogueamoeba.com/loopback/">Loopback</a> app. Nine times
out of ten, if I’m doing this it’s because I want someone to hear what a screen
reader interaction with their UI sounds like. For this, I have a premade
VoiceOver virtual device configured in Loopback. It just needs switching on, and
then it’s available as a microphone in QuickTime’s recording options.</p>

<figure>
  <video src="/2024/02/07/loopback@1024x640.mp4" poster="/2024/02/07/loopback@1024x640.webp" controls="" preload="none"></video>
  <figcaption>
    This one's complex enough that I figured it's helpful to make <a href="/2024/02/07/voiceover@1024x640.mp4" target="_blank">the resulting screen recording</a> available so that you can see for yourself that the screen reader announcement audio has been captured in the recording.
  </figcaption>
</figure>

<p>For screen reader announcements in particular, it’s often overkill to go to
these lengths. I’ve mainly found it useful when working in partnership with
blind people to evaluate and improve complex ARIA implementations. For
demonstrating screen reader announcement changes to sighted colleagues I’ve
often found it sufficient to show them
<a href="https://github.com/zetkin/app.zetkin.org/pull/1724">animated GIFs with VoiceOver’s visual announcement visible in the viewport</a>.</p>

<h2 id="give-it-a-try">Give it a try</h2>

<p>You don’t need to wait for your employer to approve budget for some expensive
3rd party SaaS screencasting platform like Loom. Fancypants extras – like
enabling you to overlay a video of yourself talking on your screencasts – might
sound like gamechangers. But I think most of the outcome in these techniques
depends on whether or not you personally engage with communication as a craft in
itself, and you don’t necessarily need to add another subscription to your
monthly bills if you’re willing to learn your way around a handful of standalone
tools.</p>

<p>Sighted people bloody <em>love</em> a visual demo, and catering to that preference is
an underutilised way to speed up feedback loops and build trust and consensus
around shipping things sooner. If you take away nothing else from this, I hope
you install Gifox today!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="accessibility" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[Screencasting is my secret weapon as a remote software engineer. I’m one of those people who just wants to go fast all the time, and you get to move a lot faster once you get good at showing people what you’ve made and putting them at ease about the idea of shipping it. At any job, one of the most common questions I hear is “How do you do all those GIFs and videos and screenshots and stuff?”. Here’s how.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/02/07/screenshot@1024x640.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/02/07/screenshot@1024x640.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Maining VSCode Terminal</title>
      <link href="https://henry.catalinismith.se/2024/01/30/maining-vscode-terminal/" rel="alternate" type="text/html" title="Maining VSCode Terminal" />
      <published>2024-01-30T00:00:00+01:00</published>
      <updated>2024-01-30T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/01/30/maining-vscode-terminal</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/01/30/maining-vscode-terminal/"><![CDATA[<p>Once upon a time, the main tools I used for work were Vim and tmux. And until
recently, once every few months I’d get this compulsive urge to try to recapture
those glory days. If you know, you know.. That urge has stopped lately though.
Visual Studio Code has reached a tipping point where the Vim support and
terminal emulator are a better package than anything I can hack together with
lua plugins and tmux config.</p>

<p>Turning VSCode into something that feels cosy for this type of retro workflow
preference doesn’t even take a lot of configuration. Here’s how I’m doing it.</p>

<h2 id="editor">Editor</h2>

<p>The editor is basically good to go once you install the
<a href="https://marketplace.visualstudio.com/items?itemName=vscodevim.vim">Vim plugin</a>
and make one or two tweaks. By default, VSCode does a lot of automatic closing
of brackets and tags and such. Some people love this. For me it just feels like
that Sorcerer’s Apprentice scene with the out-of-control magic brooms in
Fantasia, so I gotta turn it all off.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"editor.autoClosingBrackets": "never",
"editor.autoClosingDelete": "never",
"editor.autoClosingOvertype": "never",
"editor.autoClosingQuotes": "never",
"html.autoClosingTags": false,
"javascript.autoClosingTags": false,
"typescript.autoClosingTags": false,
</code></pre></div></div>

<p>That’s about it, actually. The rest is handled by
<a href="https://github.com/stars/henrycatalinismith/lists/vscode">extensions</a>.</p>

<h2 id="terminal">Terminal</h2>

<p>The terminal is where most of the tweaking is necessary. The default setup
caters for limited use cases – stuff like opening it in the morning to run
<code class="language-plaintext highlighter-rouge">yarn start</code> and then closing it in the evening. If you’re using the terminal
more like an IDE, it needs a little bit of extra juice.</p>

<h3 id="colors">Colors</h3>

<p>VSCode’s terminal has this incredible
<a href="https://code.visualstudio.com/docs/editor/accessibility#_minimum-contrast-ratio">minimum contrast ratio option</a>
that I can’t live without any more. This option alone might be enough to
convince many people to switch from other terminal emulators. Ever used a
program in the terminal whose ANSI colors contasted so little with your
background color that the text became effectively invisible? Crank up this
setting to 4.5 and VSCode will automatically detect and fix that whenever it
happens.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"terminal.integrated.minimumContrastRatio": 4.5,
</code></pre></div></div>

<p>I’ve been using Solarized for pretty much my whole career, and I love that
VSCode ships with it included. It also ships with what I consider to be
Solarized’s worst flaw: the “bright” version of the palette is a gross soup of
grays and oranges. VSCode makes it real easy to patch this though. I tend to
just duplicate the regular palette, and then trust the magic of the minimum
contrast ratio setting to fix any trouble this causes.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"workbench.colorTheme": "Solarized Dark",
"workbench.colorCustomizations": {
  "gitlens.trailingLineForegroundColor": "#93a1a1",
  "terminal.ansiBrightBlack": "#002b36",
  "terminal.ansiBrightBlue": "#268bd2",
  "terminal.ansiBrightCyan": "#2aa198",
  "terminal.ansiBrightGreen": "#9bb300",
  "terminal.ansiBrightMagenta": "#d33682",
  "terminal.ansiBrightRed": "#dc322f",
  "terminal.ansiBrightWhite": "#fdf6e3",
  "terminal.ansiBrightYellow": "#b58900"
}
</code></pre></div></div>

<h3 id="keyboard">Keyboard</h3>

<p>The keyboard shortcuts to control the terminals are the other big thing to
address. Once you’ve experienced tmux it’s hard to accept the rigidity of just
having one little panel you can toggle below the code that requires mouse
interactions for half its functionality.</p>

<p>First up is the focus management. I need to be able to toggle focus back and
forth between the editor and the terminal easily. The following
<code class="language-plaintext highlighter-rouge">keybindings.json</code> entries configure ⌘↓ to focus the terminal and ⌘↑
to focus the editor.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
	"key": "cmd+up",
	"command": "workbench.action.focusActiveEditorGroup",
	"when": "terminalFocus"
},
{
	"key": "cmd+down",
	"command": "workbench.action.terminal.toggleTerminal",
	"when": "!terminalFocus"
}
</code></pre></div></div>

<p>When I’m working in the terminal, I want common shortcuts like ⌘T and ⌘W to open
and close terminal tabs like they would in iTerm2. In VSCode there are two
contexts a terminal can be open in: either the panel, or the editor area. The
default keyboard controls for these two contexts are different, and the
following <code class="language-plaintext highlighter-rouge">keybindings.json</code> entries standardise them across both.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
  "key": "cmd+t",
  "command": "workbench.action.terminal.newInActiveWorkspace",
  "when": "terminalFocus"
},
{
  "key": "cmd+t",
  "command": "workbench.action.createTerminalEditor",
  "when": "terminalEditorFocus"
},
{
  "key": "cmd+w",
  "command": "workbench.action.terminal.kill",
  "when": "terminalFocus"
},
{
  "key": "cmd+w",
  "command": "workbench.action.terminal.killEditor",
  "when": "terminalEditorFocus"
},
</code></pre></div></div>

<p>Another quality of life thing is to have ^⇥ and ^⇧⇥ always mean “focus next tab”
and “focus previous tab”. When I’m working in the panel with multiple terminal
tabs open, I want these combos to switch over seamlessly so that they iterate
through terminal tabs instead of editor tabs. The defaults here are a <em>long</em> way
from this. This next bit of config makes ^⇥ and ^⇧⇥ do the right thing no matter
what’s focused.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
  "key": "ctrl+tab",
  "command": "workbench.action.nextEditor"
},
{
  "key": "ctrl+shift+tab",
  "command": "workbench.action.previousEditor"
},
{
  "key": "ctrl+tab",
  "when": "terminalFocus",
  "command": "workbench.action.terminal.focusNext"
},
{
  "key": "ctrl+shift+tab",
  "when": "terminalFocus",
  "command": "workbench.action.terminal.focusPrevious"
},
{
  "key": "ctrl+tab",
  "when": "terminalEditorFocus",
  "command": "workbench.action.nextEditor"
},
{
  "key": "ctrl+shift+tab",
  "when": "terminalEditorFocus",
  "command": "workbench.action.previousEditor"
}
</code></pre></div></div>

<p>Depending on the size of the VSCode window, the position of the panel might
vary. Sometimes it needs to be on the right of the editor, and sometimes below.
I do most of my window management using
<a href="https://rectangleapp.com/">Rectangle.app</a>’s keyboard shortcuts, so it’s most
efficient if I can follow one of those combos with another in VSCode. The
following bit of config sets up VSCode so that ⌘⇧→ and ⌘⇧↓ move it to
those positions.</p>

<p>This is going to be more lines of config than you’re probably expecting. The
reason for the bloat here is that this way preserves keyboard focus. If you just
run <code class="language-plaintext highlighter-rouge">workbench.action.positionPanelRight</code>, then afterwards you have to refocus
whatever bit of UI you were working in. The keybindings here detect whether
you’re invoking the shortcuts from inside a terminal or not, and ensure you land
back where you started.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
  "key": "cmd+shift+right",
  "when": "terminalFocus",
  "command": "runCommands",
  "args": {
    "commands": [
      "workbench.action.positionPanelRight",
      "workbench.action.terminal.focus"
    ]
  }
},
{
  "key": "cmd+shift+down",
  "when": "terminalFocus",
  "command": "runCommands",
  "args": {
    "commands": [
      "workbench.action.positionPanelBottom",
      "workbench.action.terminal.focus"
    ]
  }
},
{
  "key": "cmd+shift+right",
  "when": "!terminalFocus &amp;&amp; !terminalEditorFocus",
  "command": "runCommands",
  "args": {
    "commands": [
      "workbench.action.positionPanelRight",
      "workbench.action.focusActiveEditorGroup"
    ]
  }
},
{
  "key": "cmd+shift+down",
  "when": "!terminalFocus &amp;&amp; !terminalEditorFocus",
  "command": "runCommands",
  "args": {
    "commands": [
      "workbench.action.positionPanelBottom",
      "workbench.action.focusActiveEditorGroup"
    ]
  }
}
</code></pre></div></div>

<p>VSCode’s support for placing terminals in the editor area is brilliant when
you’re doing something really terminal centric. Sometimes a little panel off to
the side isn’t big enough. By default, this is a mouse-driven interaction
though. If I’m in a terminal in a bottom panel, I want ⌘⇧↑ to move it into
the editor area, hide the panel, and focus the terminal. This next bit of config
makes that happen.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
  "key": "cmd+shift+up",
  "when": "terminalFocus",
  "command": "runCommands",
  "args": {
    "commands": [
      "workbench.action.togglePanel",
      "workbench.action.terminal.moveToEditor",
      "workbench.action.focusActiveEditorGroup"
    ]
  }
},
</code></pre></div></div>

<p>For the reverse scenario, when I want to clear away my editor area terminal back
down into the panel, this entry makes ⌘⇧↓ do that.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
  "key": "cmd+shift+down",
  "when": "terminalEditorFocus",
  "command": "runCommands",
  "args": {
    "commands": [
      "workbench.action.terminal.moveToTerminalPanel",
      "workbench.action.positionPanelBottom"
    ]
  }
},
</code></pre></div></div>

<p>That’s pretty much it. It’s just enough so that I can shuffle around the
position of the panel and move terminals in and out of it using the keyboard.
Apparently that’s all I ever actually wanted. It feels pretty fantastic to have
the keyboard-driven terminal winxow management I love without sacrificing any of
the benefits that come from using the most popular tool in my corner of the
industry. Much like
<a href="/2024/01/14/my-sqlite-in-production-epiphany">using SQLite in production</a>, it
feels like it shouldn’t even be possible. I’m certainly in a real happy place
these days regarding devtools.</p>

<h2 id="future-tweaks">Future Tweaks</h2>

<p>Yesterday, I decided the final piece of the puzzle would be to have VSCode start
up with the terminal open in the editor area. I chucked
<code class="language-plaintext highlighter-rouge">vscode startupeditor terminal</code> into Google and found my way to a GitHub issue
thread titled
“<a href="https://github.com/microsoft/vscode/issues/154366"><code class="language-plaintext highlighter-rouge">workbench.startupEditor</code> should support opening a terminal in editor area</a>”.
It had a bunch of other people back in 2022 describing exactly what I was
looking for.</p>

<p>This is a type of search result I normally associate with disappointment. The
purple “Closed” badge here normally means “thanks, but no thanks”. Not this time
though. I scrolled to the bottom and could hardly believe what I was seeing: a
pull request, merged 4 days ago, titled
<a href="https://github.com/microsoft/vscode/pull/203567">Add terminal as a startup editor</a>.
OMFG! So when that commit makes it into a shipped build, the config line to use
it will be like this.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"workbench.startupEditor": "terminal",
</code></pre></div></div>

<p>Can’t wait! In the meantime, the
<a href="https://gist.github.com/henrycatalinismith/1358f66b029d8a0a358c159b1ce1c146">entire config all together</a>
is available as a gist.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[Once upon a time, the main tools I used for work were Vim and tmux. And until recently, once every few months I’d get this compulsive urge to try to recapture those glory days. If you know, you know.. That urge has stopped lately though. Visual Studio Code has reached a tipping point where the Vim support and terminal emulator are a better package than anything I can hack together with lua plugins and tmux config.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Zetkin Quote</title>
      <link href="https://henry.catalinismith.se/2024/01/22/zetkin-quote/" rel="alternate" type="text/html" title="Zetkin Quote" />
      <published>2024-01-22T00:00:00+01:00</published>
      <updated>2024-01-22T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/01/22/zetkin-quote</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/01/22/zetkin-quote/"><![CDATA[<figure>
 <img src="/2024/01/22/zetkin-quote@1080x1080.webp" alt="A person with long blonde hair stood smiling in a green coat with their back to the sea. The text reads “Meet Henry. I came into my software career through my interest in open source, which in my youthful innocence led me to expect a progressive industry driven by solidarity. Volunteering with Zetkin is the fulfillment of that dream after many years of disillusionment”" />
</figure>

<p>Zetkin have been doing little interviews with some of the volunteers lately. I
loved this promo image that came out of
<a href="https://www.linkedin.com/posts/zetkin-foundation_we-asked-henry-some-questions-about-his-life-activity-7155076637217243136-mUdQ">the interview they did with me</a>.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="zetkin" />
      

      
      
        <summary type="html"><![CDATA[Zetkin have been doing little interviews with some of the volunteers lately.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/01/22/zetkin-quote@1080x1080.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/01/22/zetkin-quote@1080x1080.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Night Work, Tech, And Swedish Labour Law</title>
      <link href="https://henry.catalinismith.se/2024/01/22/night-work-tech-and-swedish-labour-law/" rel="alternate" type="text/html" title="Night Work, Tech, And Swedish Labour Law" />
      <published>2024-01-22T00:00:00+01:00</published>
      <updated>2024-01-22T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/01/22/night-work-tech-and-swedish-labour-law</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/01/22/night-work-tech-and-swedish-labour-law/"><![CDATA[<blockquote>
  <p>This was originally posted at
https://666a.se/news/night-work-tech-and-swedish-labour-law and has been
migrated here since for long-term hosting.</p>
</blockquote>

<p>Last week’s
<a href="https://arbetet.se/2024/01/19/nattarbete-forbjuds-nu-kan-spotify-flytta-tjanster-utomlands/">news about Spotify’s night work ruling from the administrative court</a>
was a landmark moment for the Swedish tech industry. It’s the latest in a series
of news stories about tech employers trying and failing to circumvent the
Swedish labour market model through legal maneuvers. There’s a clear pattern
forming: it doesn’t work.</p>

<p>This was the original Work Environment Authority case that inspired the idea for
666a. The case had a lot of twists and turns, and it was too easy to go a week
without noticing an important new filing, so I longed for a work environment
email alert service to inform me automatically. And then when something did come
up, explaining it to people was made more difficult by the lack of linkable
English translations of relevant legislation.</p>

<p>Now that the case is drawing to a close and my dream of being able to explain
the story and its context in terms of the laws involved is finally possible,
here’s a chronological walkthrough.</p>

<h2 id="the-daily-rest-period">The daily rest period</h2>

<p><a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/section-13">Section 13 of the Swedish Working Hours Act</a>
protects the right to a daily rest period. This must be at least 11 consecutive
hours in a given 24 hour period. It must also include the hours between midnight
and 05:00.</p>

<p>This is a problem for employers whose businesses involve software systems with
the potential to break during those hours. In tech it’s commonplace to organise
an on-call rota for this work, which means engineers take turns. If something
breaks outside of normal business hours, a service like PagerDuty phones the
engineer on call and wakes them up to fix the problem.</p>

<p>This practice isn’t legal in Sweden unless certain checks and balances are in
place. Despite this, some tech employers have ignored the law, instructing
employees to work during the night anyway.</p>

<h2 id="safety-representative-training">Safety representative training</h2>

<p><a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-6-section-4">Chapter 6 Section 4 of the Swedish Work Environment Act</a>
creates a joint responsibility, shared by both employers and their employees,
that safety representatives must receive the training necessary to perform their
role.</p>

<p>Ensuring that employers’ personnel practices are compliant with
<a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450">the Working Hours Act</a>
is exactly the kind of thing safety reps are meant to do. The Swedish tech
industry’s low engagement with the country’s labour movement has enabled years
of non-compliance by many employers. Safety representatives without union
support – who may well receive all their training from the employer despite the
clear conflict of interest this entails – often won’t notice the violation, or
are easily instructed to ignore it.</p>

<p>This pattern is exactly why the government’s 2023 instructions to the work
environment authority to increase the number of safety reps “regardless of union
membership status” met with so much
<a href="https://www.arbetsvarlden.se/regeringen-oppnar-for-fler-skyddsombud-utan-facktillhorighet/">backlash from the central unions</a>.
Safety reps without union support are easily sidelined. It’s my understanding
that cases of full-blown employer co-optation of entire work environment
committees aren’t unheard of.</p>

<h2 id="exemptions-by-the-work-environment-authority">Exemptions by the Work Environment Authority</h2>

<p><a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/section-19">Section 19 of the Swedish Working Hours Act</a>
gives the Work Environment Authority the power to grant exemptions to
<a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/section-13">the night work ban imposed by section 13</a>.
In
<a href="https://www.av.se/om-oss/sok-i-arbetsmiljoverkets-diarium/?id=2023/016334-18">2023/016334-18</a>,
they explained the criteria for the exemptions in some detail.</p>

<blockquote>
  <p>It appears from practice that the exemption option regarding the night work
ban must be applied restrictively and is intended for situations that satisfy
important societal interests and urgent service needs.</p>

  <p>The work that is granted an exemption from the night work ban thus either
needs to support a directly exempt activity or in itself be of great societal
interest. It is also required that there is an important societal interest in
the work being carried out at night.</p>
</blockquote>

<p>In blunt terms, “we make more money when the servers are up” doesn’t cut it. If
you’re a SaaS business and one of your customers depends on your product to
deliver a socially critical service then you can still get an exemption because
of that, but otherwise there’s only one other route to legal night work.</p>

<h2 id="legal-night-work-through-collective-bargaining">Legal night work through collective bargaining</h2>

<p><a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/section-3">Section 3 of the Swedish Working Hours Act</a>
grants workers the flexibility to negotiate away
<a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/section-13">the night work ban imposed by section 13</a>
through collective bargaining with the employer.</p>

<p>This is one of my personal favourite Swedish laws. I think there’s just such an
instinctive fairness to it. To agree to be randomly awoken in the night to
urgently defend your employer’s business is to take a level of responsibility
more commonly associated with a parent of a newborn baby. It’s fair for workers
willing to invest this much of themselves in a business to expect their employer
to respect them as an equal counterpart in negotiations about working conditions
and the like.</p>

<p>This is the point Spotify’s founding team missed when
<a href="https://medium.com/@SpotifySE/vi-m%C3%A5ste-agera-eller-bli-omsprungna-383bb0b808eb#.u5h5pqrva">they first threatened to withdraw the company from Sweden back in 2016</a>.</p>

<blockquote>
  <p>Think how amazing it would be if Sweden became the country where the employees
became part-owners of the companies where they work. It suits us and our
culture perfectly. We as owners are prepared to share. If anything, politics
should promote that ownership spreads and creates solidarity.</p>
</blockquote>

<p>It sounds alright
<a href="https://www.theguardian.com/technology/2023/apr/11/techscape-zirp-tech-boom">superficially</a>,
but the words turns to ashes in your mouth once you understand that small-time
shareholders don’t have any meaningful say in working conditions compared to the
co-determination already provided for by Sweden’s world-leading labour law. Once
you’re being woken up at 03:00 to fix servers, the board’s fiduciary duty to
shareholders may even be in conflict with your most pressing material needs
(i.e. a full night’s sleep as often as possible). That’s why I think
<a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/section-3">section 3</a>
is such a clever piece of legal judo to empower workers. You’re such a key
worker that you sometimes need to get up in the night like a superhero? Then you
get a seat at the table when big decisions are being made.</p>

<h2 id="fines-for-non-compliance">Fines for non-compliance</h2>

<p><a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/section-24">Section 24 of the Swedish Working Hours Act</a>
imposes fines for employers who violate
<a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/section-13">the night work ban imposed by section 13</a>.</p>

<p>I’m not as impressed with this law as I am with the flexibility of
<a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/section-3">section 3’s compliance via collective bargaining</a>.
Klarna’s fine was
<a href="https://kollega.se/arbetsmiljo/efter-arbetsmiljobrister-klarna-maste-betala-miljonbelopp">1,094,471kr</a>
and Spotify’s was only
<a href="https://kollega.se/arbetsratt/spotify-tvingas-betala-nattarbete-saknade-avtal">356,000kr</a>.
Fines this small are not a serious financial deterrent.</p>

<p>There is also the soft penalty of the brand damage an employer incurs by
flouting labour laws, but most employers aren’t as high profile as Klarna or
Spotify, so this penalty is much smaller for them. And even in cases where this
leverage exists, it’s a severe flaw for the system to depend on individual union
representatives or safety reps taking on the personal risk of actually using
that leverage to achieve compliance. Fear of retaliation has a very powerful
chilling effect.</p>

<h2 id="the-right-to-appeal">The right to appeal</h2>

<p><a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/section-29">Section 29 of the Swedish Working Hours Act</a>
makes it possible to appeal the Work Environment Authority’s decisions in an
administrative court.</p>

<p>Something I’ll wonder about for the rest of my days is how much impact the
events of the Tesla strike had on Spotify’s appeal. In a regular year, I reckon
the sheer brand power that Spotify wields in Swedish society might have been
enough to browbeat an off-guard administrative court into granting their appeal.</p>

<p>But 2023 wasn’t a regular year. Of particular note were
<a href="https://da.se/2023/12/annu-ett-bakslag-for-tesla-postnord-behover-inte-lamna-ut-skyltar/">Tesla’s failed Solna district court case</a>
and their
<a href="https://www.domstol.se/nyheter/2023/12/hovratten-upphaver-tingsrattens-tillfalliga-beslut-att-lata-tesla-hamta-registreringsskyltar-direkt-hos-tillverkaren/">loss on appeal in the Norrköping district court</a>.
I can’t shake the feeling that all this racket Tesla made might have awoken an
awareness among Sweden’s legal profession that a couple of powerful employers
were attempting a sudden Americanisation of the Swedish labour market through
courtroom shenanigans.</p>

<h2 id="enforcement">Enforcement</h2>

<p><a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/section-22">Section 22 of the Swedish Working Hours Act</a>
empowers the Work Environment Authority to issue any prohibitions necessary to
get employers to follow the law.
<a href="https://lagstiftning.codeberg.page/arbetstidslagen/2022:450/section-23">Section 23</a>
imposes penalties ranging from fines to up to a year of imprisonment for failure
to comply with prohibitions under section 22.</p>

<p>It seems unlikely the Spotify case will come to this. Their stance is clear:
they’ll instruct their overseas workforce to pick up these tasks rather than
engage in collective bargaining with their Swedish workforce.</p>

<p>I think that’s a really exciting move – part of a strategy conceived in early
2023, when they filed for their night work ban exemption the day after the
formation of the Spotify Unionen club. Back then, it had been years since
Unionen had last invoked
<a href="https://lagstiftning.codeberg.page/medbestammandelagen/2021:1114/section-45">section 45 of the Swedish Co-Determination Act </a>,
which I bet made this seem like more of a risk analysis slam dunk than it might
seem now.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[This was originally posted at https://666a.se/news/night-work-tech-and-swedish-labour-law and has been migrated here since for long-term hosting.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">My SQLite in production epiphany</title>
      <link href="https://henry.catalinismith.se/2024/01/14/my-sqlite-in-production-epiphany/" rel="alternate" type="text/html" title="My SQLite in production epiphany" />
      <published>2024-01-14T00:00:00+01:00</published>
      <updated>2024-01-14T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/01/14/my-sqlite-in-production-epiphany</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/01/14/my-sqlite-in-production-epiphany/"><![CDATA[<p>My work environment email alerts service – <a href="https://666a.se/">666a</a> – is a Rails
app using SQLite for its production database. I’m super happy with this stack,
but it wasn’t an obvious choice from the start, and I know a lot of folks still
haven’t heard the growing hype about using SQLite in production in the Rails
community. Here’s how I ended up shipping a production app with a SQLite
database, and how it helped me rediscover some joy in full-stack work.</p>

<h2 id="back-on-rails">Back on Rails</h2>

<p>I built the very first prototype version of 666a in Rails. Nothing beats Active
Record when you need to iterate really quick on a concept for a database schema.
It ran on our old MacBook Air where our Plex server lives. Then, in late August,
I got sidetracked by the idea that for the “real” version I needed to redo it in
a more “modern” web stack like Next.js.</p>

<p>What a waste of a month this turned out to be. You have to jump through so many
hoops for this new edge computing paradigm, but none of the benefits are
applicable to an app like 666a with relatively stable load whose users are
concentrated in one geographic location. Plus, the core things 666a actually
<em>needs</em> – mundane stuff like an ORM or a nice mature localisation framework –
are all third party plugins in Next.js. Afterthoughts.</p>

<p>I churned through so many libraries and platforms trying to make this mismatch
work. The ones I can remember include <a href="https://kysely.dev/">Kysely</a>,
<a href="https://www.prisma.io/">Prisma</a>, <a href="https://planetscale.com/">Planetscale</a>,
<a href="https://vercel.com/docs/storage/vercel-postgres">Vercel Postgres</a>, and
<a href="https://www.inngest.com/">Inngest</a>. It seemed like no matter what I picked, it
was going to get expensive fast, and yet at the same time nothing’s fully
working right yet because the whole edge functions thing is still so bleeding
edge.</p>

<p>By the end of September I was fed up. I deleted my Next.js app and ran
<code class="language-plaintext highlighter-rouge">rails new</code> with some considerable relief.</p>

<h2 id="but-sqlite-in-production">But… SQLite? In production?</h2>

<p>As I was making that decision to jump back to Rails, I was aware of a growing
wave of hype about using SQLite in production. I eagerly followed Stephen
Margheim’s series of
<a href="https://fractaledmind.github.io/tags/sqlite/">SQLite-related blog posts</a>,
<a href="https://blog.appsignal.com/2023/09/27/an-introduction-to-litestack-for-ruby-on-rails.html">AppSignal’s series on LiteStack</a>,
as well as
<a href="https://fly.io/ruby-dispatch/sqlite-and-rails-in-production/">anything Fly.io published about it</a>.</p>

<p>This used to be unthinkable. In fact it’s only in the past month that the Rails
core team
<a href="https://github.com/rails/rails/pull/50463">removed the warning message about running SQLite in production</a>.
I reckon it’s a combination of factors: it’s enabled by technological advances,
and driven by a reduced focus on exponential growth due to high interest rates.</p>

<p>Whatever the underlying reason for the mindset shift, it doesn’t seem to be news
to the folks behind SQLite itself. The quote below has been on their site
<a href="https://web.archive.org/web/20050204022544/https://www.sqlite.org/whentouse.html">since at least 2005</a>.</p>

<blockquote>
  <p>SQLite works great as the database engine for most low to medium traffic
websites (which is to say, most websites). Generally speaking, any site that
gets fewer than a 100,000 hits/day should work fine with SQLite.</p>
</blockquote>

<p>666a is right in the centre of the sweet spot for a SQLite in production
use-case. It’s a service targeting a niche interest group in a tiny country of
10 million people. I reckon the entire Swedish labour movement could sign up
before I’d even need to starting googling for performance tips.</p>

<h2 id="the-experience-so-far">The experience so far</h2>

<p>It’s rare for me to find this much joy in the back-end &amp; infrastructure side of
a project. Normally it’s the front-end work that gives me these feelings, but
there’s something truly special about this tech stack. And the fact that it’s
cheaper – 666a currently fits within the Fly.io free tier – is only part of it.</p>

<p>The sensation of being able to ship an entire full-stack app as a single Docker
image and not even have a <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> at all is difficult to describe.
There’s something weirdly cosy about it. It also feels like it shouldn’t even be
possible, and brings back nostalgic memories of the 2000s-era trend for PHP
features to use a “flat file” for storage instead of a database.</p>

<p>Four months in, there have been no downsides to speak of. It’s literally just…
fantastic. My database is around 350MB, doing something on the order of 1000ish
writes and (<em>really</em> guessing now) 10,000ish reads per day. It’s got indexes,
I’m doing joins and group by statements and all that normal stuff, and I’m never
being confronted with unexpected barriers resulting from the choice of SQLite.</p>

<p>In fact I’m even using
<a href="https://fractaledmind.github.io/2023/09/22/enhancing-rails-sqlite-ulid-primary-keys/">ULID primary keys</a>
cos I didn’t want the infosec headaches of autoincrementing integers (any labour
movement-related software always deserves a bit of paranoia) or the awkward
ergonomics of regular UUIDs. Setting that up was a piece of piss too.</p>

<p>The contrast I find the most striking in all this is that in Next.js I was just
setting up a normal app the normal way, yet somehow still it felt like swimming
against the tide, whereas with Rails, even configuring a quirky primary key
format on a non-standard database was easy and actually kind of fun.</p>

<p>When you consider the fact that I have zero professional Rails experience, and
that I’ve spent the better part of a decade working mostly with React, it does
seem a bit weird. I mean I even really <em>like</em> React for fuck’s sake. Seems silly
that it’s somehow not the easiest way for me to ship a simple app like this one.</p>

<p>My gut feeling is that it’s partly an incentives thing. Vercel’s in the PaaS
business, so obviously the product strategy for Next.js is wrapped up in that
and it makes sense to prioritise promoting the serverless paradigm they’ve bet
their entire company on over e.g. integrating a production-ready i18n solution.
By contrast, nothing about 37signals’ SaaS business incentivises Rails to double
as the top of a freemium conversion funnel.</p>

<p>Nothing showcases that difference in incentives better than production SQLite.
We use Next.js at my current job, where it’s a great fit, and I think I kinda
get it: this new serverless stuff is way more worth the trouble when you’re
hoping to scale up as fast and as big as possible. For more modest projects with
smaller troubleshooting time budgets, maybe it’s natural that a tool built by
and for the VC ecosystem doesn’t fit so great. It feels great that this
production SQLite adventure has helped me understand all this a bit better, and
after a decade of React I’m stoked about the prospect of having a new go-to
stack with more batteries included.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="databases" />
      
        <category term="devtools" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[My work environment email alerts service – 666a – is a Rails app using SQLite for its production database. I’m super happy with this stack, but it wasn’t an obvious choice from the start, and I know a lot of folks still haven’t heard the growing hype about using SQLite in production in the Rails community. Here’s how I ended up shipping a production app with a SQLite database, and how it helped me rediscover some joy in full-stack work.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Operating 666a</title>
      <link href="https://henry.catalinismith.se/2024/01/09/operating-666a/" rel="alternate" type="text/html" title="Operating 666a" />
      <published>2024-01-09T00:00:00+01:00</published>
      <updated>2024-01-09T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2024/01/09/operating-666a</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2024/01/09/operating-666a/"><![CDATA[<p>In the final quarter of 2023 I launched my little side project
<a href="https://666a.se/">666a</a>, which is a service providing work environment email
notifications for union reps and people like that. It took a few months of work
to build something shippable, and I’ve been refining it ever since to increase
its reliability. I thought it’d be nice to share a glimpse of what that
refinement work looks like. This is gonna be a technical one, so apologies to
anyone who preferred the union propaganda.</p>

<h2 id="how-666a-works">How 666a works</h2>

<p>The beating heart of 666a is a daily cron job that checks for new public filings
on the Work Environment Authority’s website and sends email notifications
whenever it finds one matching a user’s subscription criteria.</p>

<p>Filings are published with one day of delay, which means Monday’s filings become
available on Tuesday and so on. So each day, 666a scans the filings from the
previous day.</p>

<h2 id="backdated-filings-and-document-lag">Backdated filings and “document lag”</h2>

<p>The complicating factor here is that not all Monday’s filings are immediately
available on Tuesday. In fact, they might not even be filed at all until next
Friday. This is a phenomenon I’ve been referring to as “document lag”. The Work
Environment Authority isn’t an API: it’s an organisation powered by people,
interactions, emails, and even good old fashioned physical mail. This makes for
a much more socially constructed model of time than I’m used to in my everyday
tech work where machine-generated timestamps with millisecond accuracy are the
norm.</p>

<p>I didn’t spot this nuance until after launch.
<a href="https://666a.se/incident-report">I take the responsibility of operating this service really really seriously</a>,
and I’m not comfortable with the idea of anyone missing out on important
information because of a detail like that. So I’ve been slowly tweaking the
design of the system to meet this requirement.</p>

<h2 id="manually-triggered-backfill">Manually triggered backfill</h2>

<p>My first move once I understood the system was missing some of the data was to
start manually triggering the scan job from time to time. I was very deliberate
about designing the actual jobs around this kind of versatility, so a bit of
manual intervention was easy and didn’t require any code changes. This is more
or less what I’d run in the rails console from time to time.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">WorkEnvironment</span><span class="o">::</span><span class="no">DayJob</span><span class="p">.</span><span class="nf">perform_now</span><span class="p">(</span><span class="s2">"2023-11-04"</span><span class="p">)</span>
</code></pre></div></div>

<p>Doing this every day was a labour-intensive approach, but it was a nice way to
feel out the scope of the problem. Took about a week for this to give me a nice
clear sense of how far back stuff was typically getting backdated.</p>

<h2 id="scheduled-backfill">Scheduled backfill</h2>

<p>Based on that informal feel for the typical lag between document filing dates
and their actual first appearance, I began scheduling a bunch of backfill jobs
like these.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">scheduler</span><span class="p">.</span><span class="nf">cron</span><span class="p">(</span><span class="s2">"0 14 * * *"</span><span class="p">)</span> <span class="k">do</span>
  <span class="no">WorkEnvironment</span><span class="o">::</span><span class="no">DayJob</span><span class="p">.</span><span class="nf">perform_later</span><span class="p">(</span>
    <span class="mi">2</span><span class="p">.</span><span class="nf">days</span><span class="p">.</span><span class="nf">ago</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s2">"%Y-%m-%d"</span><span class="p">)</span>
  <span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>

<p>These have been running nicely since early December. For most days, the average
document lag is only about a day or two. So except for the occasional rogue
document filed 30 days after its official filing date, this was mission
accomplished.</p>

<h2 id="christmas">Christmas</h2>

<p>The arrival of Christmas unlocked a new level of this puzzle. Suddenly, my daily
document count graph flatlined. Everybody had buggered off to watch
<a href="https://en.wikipedia.org/wiki/From_All_of_Us_to_All_of_You">Donald Duck</a>, and
even the steady everyday drumbeat of the
<a href="https://www.av.se/om-oss/sok-i-arbetsmiljoverkets-diarium/?OrganisationNumber=559068-7967">asbestos removal company filings</a>
fell silent.</p>

<figure>
  <img src="/2024/01/09/december-2023@1820x600.webp" alt="
      Bar chart with y axis going up to 1500 and x axis of dates ranging across December 2023.
      The first three weeks of the month average between 500 and 1000 documents per weekday.
      The final week averages zero.
    " />
  <figcaption>
    Documents per day in December 2023, as of New Year's Eve
  </figcaption>
</figure>

<p>This would be no big deal, except by this point I’d also backfilled the data
well into 2022. And the December 2022 graph’s final week looked nothing like
this.</p>

<figure>
  <img src="/2024/01/09/december-2022@1820x600.webp" alt="
      Bar chart with y axis going up to 1000 and x axis of dates ranging across December 2022.
      The first three weeks of the month average between 500 and 1000 documents per weekday.
      The final week averages 250.
    " />
  <figcaption>
    Documents per day in December 2022
  </figcaption>
</figure>

<p>I immediately suspected that this was another case of document lag. In other
words everyone’s off for Christmas, and when they get back they’ll backdate all
the filings that were missed during the holidays.</p>

<h2 id="quantifying-document-lag">Quantifying document lag</h2>

<p>The only way to find out if my document lag theory was the right explanation was
to ignore the problem, enjoy Christmas, and wait and see. It probably didn’t
matter anyway: I don’t think many world-changing public filings are dropping
during the Christmas season.</p>

<p>I did want some way to visualise the issue when the backdated filings began to
trickle in though. So I created a new chart displaying the document lag for each
day since launch. The bar height here represents the average number of days
difference between that date and the true chronological first appearance of the
documents officially filed on that date.</p>

<figure>
  <img src="/2024/01/09/document-lag@1820x600.webp" alt="
      Bar chart with y axis going up to 10 and x axis of dates ranging across Q3 of 2023.
      Until late December the values average at one or two with occasional blips.
      After December averages above 5 are more tpical.
    " />
  <figcaption>
    Average document lag per day since launch day
  </figcaption>
</figure>

<p>The Christmas spike represents the fact that the documents officially filed on
e.g. December 27th were in fact not filed until January 3rd when people began
returning to work.</p>

<h2 id="next-steps">Next Steps</h2>

<p>I think the scale of this phenomenon will probably more or less double for
Midsummer, so that’s what I want to be prepared for next. Once the dust settles
from the Christmas period, I’m thinking I’ll figure out the maximum document lag
measured in that week, double it, and then wire up the scheduler to backfill
that far back every single day. I want Midsummer to run smoothly even if I’m too
busy to do any manual interventions in the moment.</p>

<p>By the way, if you’re a tech worker in Sweden and the events of the past year or
so have got you thinking some new thoughts about labour relations in our
industry, you might wanna check out 666a more closely. You can grab your
employer’s <em lang="sv">organisationsnummer</em> from
<a href="https://foretagsinfo.bolagsverket.se/sok-foretagsinformation-web/foretag">Bolagsverket’s company search tool</a>
and then
<a href="https://666a.se/register">subscribe to get notified (for free) about their correspondence with the Work Environment Authority</a>.
I’m also hosting web versions of the Swedish government’s unofficial English
translations of the
<a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/">Work Environment Act</a>,
the
<a href="https://lagstiftning.codeberg.page/medbestammandelagen/2021:1114/">Co-Determination Act</a>
and the
<a href="https://lagstiftning.codeberg.page/lagen-om-anstallningsskydd/2022:836/">Employment Protection Act</a>,
which can be useful resources to know about depending on how 2024 unfolds at
your job. Check it out!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[In the final quarter of 2023 I launched my little side project 666a, which is a service providing work environment email notifications for union reps and people like that. It took a few months of work to build something shippable, and I’ve been refining it ever since to increase its reliability. I thought it’d be nice to share a glimpse of what that refinement work looks like. This is gonna be a technical one, so apologies to anyone who preferred the union propaganda.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2024/01/09/december-2023@1820x600.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2024/01/09/december-2023@1820x600.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Sam’s Boat</title>
      <link href="https://henry.catalinismith.se/2023/12/19/sams-boat/" rel="alternate" type="text/html" title="Sam’s Boat" />
      <published>2023-12-19T00:00:00+01:00</published>
      <updated>2023-12-19T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2023/12/19/sams-boat</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2023/12/19/sams-boat/"><![CDATA[<figure>
 <img src="/2023/12/19/sams-boat@1170x1170.webp" alt="Minecraft screenshow featuring a pixelated wooden boat in some shallow water in the shade of a cherry tree and an oak tree with a wooden bridge in the background." />
</figure>

<p>Sam likes the boats in Minecraft. This is one of his boats.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2023/12/19/sams-boat@1170x1170.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2023/12/19/sams-boat@1170x1170.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">It won’t grow if you don’t delegate</title>
      <link href="https://henry.catalinismith.se/2023/12/09/it-wont-grow-if-you-dont-delegate/" rel="alternate" type="text/html" title="It won’t grow if you don’t delegate" />
      <published>2023-12-09T00:00:00+01:00</published>
      <updated>2023-12-09T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2023/12/09/it-wont-grow-if-you-dont-delegate</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2023/12/09/it-wont-grow-if-you-dont-delegate/"><![CDATA[<p>The first time I read <a href="https://organizing.work/2022/01/it-wont-grow-if-you-dont-delegate/">It won’t grow if you don’t delegate</a>, I was at
Vänsterpartiet Malmö’s <a href="https://malmo.vansterpartiet.se/event/studier-for-forandring/">studier för förändring</a> course at Kvarnby folkhögskola.
It had been translated into Swedish and printed onto paper, and it was all very
exciting.</p>

<p>The article itself’s a must-read for any new organiser. The leadership stuff it
covers is similar to what you learn when you go into management, but
re-contextualised for the labour movement. It felt great to have stumbled on
this key bit of knowledge and I was dead excited to have become a little bit
more dangerous.</p>

<p>Thing is, in hindsight it’s pretty clear that the message didn’t sink in
completely. Like I think I’d understood it superficially, at an intellectual
level, but emotionally I hadn’t bought in fully. It’s not as if this was
anywhere near the campaign’s biggest problem or setback, but it’s probably the
one thing I had the most personal control over that could have gotten us even
further sooner if I’d been more on top of it.</p>

<p>Looking back, there were some warning signs that I missed. I feel like writing
them down. I’d love if it could help someone else’s campaign gain ground faster
by enabling them to internalise this lesson earlier.</p>

<h2 id="warning-signs-of-my-failure-to-delegate">Warning Signs Of My Failure To Delegate</h2>

<h3 id="warning-sign-1-exhaustion">Warning Sign #1: Exhaustion</h3>

<p>Sounds obvious, doesn’t it? Like if you’re working your fingers to the bone and
feel like the intensity might <em>actually</em> be killing you, the delegation light
should just automatically switch on in your head, right?</p>

<p>Didn’t happen for me. You think you’re immune to all that “rise &amp; grind”
glorification of overwork until suddenly you realise you’re arsehole deep in it
and hadn’t even noticed it happening.</p>

<p>I’ve come to understand that behaviours that feel intuitively toxic in a
profit-driven context can seem like righteous self-sacrifice in a union
campaign. The fact that I wasn’t making any money in the process biased me away
from noticing any similarities between the choices I was making and the
workaholic loner tech industry stereotype I’d spent my career trying to escape.</p>

<h3 id="warning-sign-2-passive-aggressive-delegation">Warning Sign #2: Passive Aggressive Delegation</h3>

<p>There were a few occasions when I just totally fucking imploded. I remember one
time in particular when I was in the middle of the redundancy process and a
total nervous wreck about it, and had some kind of outburst to the rest of the
group.</p>

<p>I was overwhelmed and failing real fuckin hard at any kind of emotional
regulation. Instead of taking a little inventory of the stuff to do and figuring
out what to offload, I just expressed my general desperation and panic to the
group. An ugly moment.</p>

<p>This was a lovely bunch of folks, and I got some great support out of that. But
instead of getting that support by doing actual pro-active delegation like a
proper leader, it had happened the opposite way around, by me catching fire and
the others rushing to rescue things from the flames.</p>

<h3 id="warning-sign-3-denial">Warning Sign #3: Denial</h3>

<p>At some point the penny did drop. I wasn’t delegating enough and it was hurting
the campaign, not to mention hurting me. It’s interesting now – with some
distance – to reflect on how even after <em>that</em> realisation I still managed to
avoid learning the lesson completely.</p>

<p>There’s just so fucking <em>many</em> ways to bullshit yourself. “This one would be too
much to ask anyone else to do”, “that person’s too busy for this”, “I already
delegated that one other thing so now I’m lazy if I delegate this too”, “they
didn’t sign up for this level of commitment”, “this task’s scary, and the risky
stuff is <em>my</em> job”.</p>

<p>The first two warning signs feel more like once-and-done lessons learned, but I
reckon this last one is more of an ongoing process. Might be more like something
I always have to look out for, rather than a to-do list item that can be crossed
off forever at some point. But these denial thoughts have a certain recognisable
smell to them, and once that recognition’s locked in real good I think it’s
harder for them to have as much influence.</p>

<h2 id="delegations-hard">Delegation’s Hard</h2>

<p>I have a theory that it’s a very particular type of intense person who tends to
find themself leading a union campaign. I’ve certainly met a good few of them by
now. Maybe a little less interested in money than a typical entrepreneur, but
similar in terms of risk tolerance and work ethic.</p>

<p>That work ethic part can be a real Achilles’ heel, I reckon. If you’re used to
getting a lot of your sense of who you are out of the pride you take in your
productivity, it’s easy to mistake delegation for laziness.</p>

<p>In a business leadership context, you’d always have someone “above” you to coach
you through this stuff. In a union campaign it’s not so simple. I feel like if
I’d been able to read something along these lines in addition to <a href="https://organizing.work/2022/01/it-wont-grow-if-you-dont-delegate/">It won’t grow
if you don’t delegate</a> back in November 2022, I might have been more primed to
learn this lesson sooner. Hopefully someone else stumbles on it in advance of
their own campaign and benefits from it.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[The first time I read It won’t grow if you don’t delegate, I was at Vänsterpartiet Malmö’s studier för förändring course at Kvarnby folkhögskola. It had been translated into Swedish and printed onto paper, and it was all very exciting.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Incident Report</title>
      <link href="https://henry.catalinismith.se/2023/12/03/incident-report/" rel="alternate" type="text/html" title="Incident Report" />
      <published>2023-12-03T00:00:00+01:00</published>
      <updated>2023-12-03T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2023/12/03/incident-report</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2023/12/03/incident-report/"><![CDATA[<blockquote>
  <p>This was originally posted at https://666a.se/news/incident-report and has
been migrated here since for long-term hosting.</p>
</blockquote>

<p>This weekend there was a minor incident where 666a over-alerted a little bit,
resulting in a total of less than 100 email alerts to be sent in error. No data
loss or privacy breaches occurred as part of this incident, just a few emails
getting sent that shouldn’t have.</p>

<p>Here’s the explanatory email I sent to the folks who got some unnecessary
emails.</p>

<blockquote>
  <p>Hey folks</p>

  <p>Earlier today 666a went a bit haywire and started sending loads of email
notifications about arbetsmiljöverket filings from April. It’s fixed now and
you shouldn’t see any more.</p>

  <p>I take operating this service really seriously and wanted to get in touch and
explain. I’ve been tweaking the diarium update algorithm this week to try to
avoid missing documents if they’re filed late and then backdated to earlier in
the week. While doing so I made a small coding mistake that turned on email
notifications for all documents.</p>

  <p>Normally only a fraction of documents get email notifications sent about them.
I’ve got 666a slowly backfilling its database with the entire 2023 dataset
because I think I can operate the service best with at least one complete year
of data history. Email notifications are switched off for those historical
documents but my change this week accidentally turned them on.</p>

  <p>Rest assured it won’t happen again, and as you can tell I’m very biased
towards overalerting rather than ever risk someone missing something, so I
think that’s at least not completely terrible.</p>

  <p>Henry</p>
</blockquote>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[This was originally posted at https://666a.se/news/incident-report and has been migrated here since for long-term hosting.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">You Dumb Son Of A Bitch</title>
      <link href="https://henry.catalinismith.se/2023/11/19/you-dumb-son-of-a-bitch/" rel="alternate" type="text/html" title="You Dumb Son Of A Bitch" />
      <published>2023-11-19T00:00:00+01:00</published>
      <updated>2023-11-19T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2023/11/19/you-dumb-son-of-a-bitch</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2023/11/19/you-dumb-son-of-a-bitch/"><![CDATA[<video controls="" preload="none" poster="/2023/11/19/you-dumb-son-of-a-bitch@640x360.webp">
 <source src="/2023/11/19/you-dumb-son-of-a-bitch@512x288.mp4" type="video/mp4" />
</video>

<p>Made this lil remix in iMovie on my phone. Free Palestine, you dumb son of a
bitch.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="video" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2023/11/19/you-dumb-son-of-a-bitch@640x360.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2023/11/19/you-dumb-son-of-a-bitch@640x360.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">English translations of Swedish laws</title>
      <link href="https://henry.catalinismith.se/2023/11/14/english-translations-of-swedish-laws/" rel="alternate" type="text/html" title="English translations of Swedish laws" />
      <published>2023-11-14T00:00:00+01:00</published>
      <updated>2023-11-14T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2023/11/14/english-translations-of-swedish-laws</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2023/11/14/english-translations-of-swedish-laws/"><![CDATA[<blockquote>
  <p>This was originally posted at
https://666a.se/news/english-translations-of-swedish-laws and has been
migrated here since for long-term hosting.</p>
</blockquote>

<p>The work environment email alerts launch a couple of weeks ago has gone great.
It’s operating really smoothly now and delivering email alerts to way more
people than I dreamed would sign up this early. So I’m moving to the next phase,
which begins today.</p>

<p>Linked here is an
<a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-6-section-6a">English translation of chapter 6, section 6a of the Swedish Work Environment Act</a>.
This is the law that 666a is named after. Until today the English version of it
hasn’t had its own URL. You could link to PDFs containing it somewhere on page
15, or pages that mention it in passing, but there was no way to send your
English-speaking colleague a convenient link to it to help them understand their
rights.</p>

<p>The data entry work that’s gone into creating this resource was gruelling. The
reason it was worth the trouble is simple. I saw first-hand during the Spotify
campaign how the absence of this kind of resource is a strategic weakness for
Sweden’s labour movement, with its dreams of organising the country’s
influential tech immigrant workforce.</p>

<p>People move to this country to work in tech. Sweden’s bursting with cool
international employers whose company language is English. So you get off the
plane on Saturday, unpack on Sunday, roll into the office on Monday and just get
started. No language barrier, everything’s in perfect English.</p>

<p>Later that week you stop by the bank during lunch to try to open an account. Not
so easy. The person helping you speaks great English, but the forms are in
Swedish. It takes longer than expected and makes you return late from lunch.
Right from the start, your new life in Sweden is defined by this contrast
between the comfort and familiarity provided by your employer and the friction
and inconvenience of Swedish society at large. It’s a barrier to integration.</p>

<p>Ten years ago, the Swedish government produced unofficial English translations
of key laws. These were published as PDFs on government.se, where they remain to
this day. The laws themselves have been updated repeatedly since then, but not
all the translations have been updated to match. To a lawyer, the failure to
maintain such an important resource might seem like the headline problem. But in
tech culture, it’s actually the PDF thing.</p>

<p>It’s impossible to send someone a link to a specific part of a PDF, which means
sending a colleague a link to e.g. section 11 of the Co-determination Act in
English is impossible. You have to send them the PDF link and tell them “scroll
down to xyz” instead. Many people won’t.</p>

<p>And the dominant mobile device among this demographic – the iPhone – provides no
immediate means of generating a machine translation if you send someone a link
to a resource in Swedish. They can copy-paste the text into Google Translate if
they’re <em>really</em> keen. But many people won’t.</p>

<p>The experience of receiving a PDF link is worse too. A web page will usually
have metadata configured so that a link pasted into a chat platform such as
WhatsApp or Slack will appear with a short description or excerpt of the page’s
content. PDFs can’t do this, so on most platforms you just see a bare link
instead and have to make a leap of faith that it might contain something useful.
Many people won’t.</p>

<p>This friction is a serious barrier to increasing engagement. Tech people <em>live</em>
on the internet in a way that politicians and lawyers just <em>don’t</em>. In our
interactions with online services we perceive subtle signs of value or quality
or trustworthiness that people who are less terminally online might not.</p>

<p>Starting today, 666a is taking one of those decade-old translations and hosting
it as proper linkable web content. You can browse the
<a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349">full text of the Work Environment Act in Swedish and English side by side</a>.
And when you find the section you’re looking for, you can grab its URL for
bookmarking or sharing. So finally there’s a way to send someone a link to
<a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349/chapter-6-section-6a">chapter 6 section 6a in English</a>.</p>

<p>The decision to include the original Swedish alongside the unofficial English
translation is deliberate too. Those little moments of exposure will help grow
your vocabulary. Integration isn’t just about spoon-feeding us translations
forever. Any immigrant’s long-term goal is always language acquisition.
Thoughtful design choices in services we depend on can support that long-term
goal even as we focus on solving our short-term everyday problems.</p>

<p>I’ll repeat this process for more laws over the coming weeks. There are some
particularly important ones that are my top priority, like the Co-Determination
Act and the Employment Protection Act. Please do email me at henry@666a.se and
let me know if there’s one you think is important not to miss.</p>

<p>My big dream for this feature would be to secure some funding from somewhere to
spend on updating all the translations. I think there’s almost limitless
potential here. For example, I would absolutely love to commission professional
Arabic translations of all these laws and host them right here alongside the
English ones. I’m convinced that meeting immigrants where we are is the way to
make us feel seen and wanted, and inspire us to engage more with Swedish
society.</p>

<p>But first things first. Here’s an
<a href="https://lagstiftning.codeberg.page/arbetsmiljolagen/2023:349">English copy of the Work Environment Act</a>
that’s ten years out of date.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[This was originally posted at https://666a.se/news/english-translations-of-swedish-laws and has been migrated here since for long-term hosting.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Announcing 666a</title>
      <link href="https://henry.catalinismith.se/2023/10/31/announcing-666a/" rel="alternate" type="text/html" title="Announcing 666a" />
      <published>2023-10-31T00:00:00+01:00</published>
      <updated>2023-10-31T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2023/10/31/announcing-666a</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2023/10/31/announcing-666a/"><![CDATA[<blockquote>
  <p>This was originally posted at https://666a.se/news/launch-announcement and has
been migrated here since for long-term hosting.</p>
</blockquote>

<p>Public filings at the Swedish Work Environment Authority are essential for
understanding organizations and the current work environment of companies.
Keeping up to date is a prerequisite for driving sustainable development in the
Swedish labour market. 666a is a modernization of Sweden’s principle of public
access to information, making information automatic, customized, and
multilingual, rather than merely available.</p>

<h2 id="origin">Origin</h2>

<p>The idea for this tool comes from personal experience with union involvement in
the workplace. As a union representative at Spotify, a problem I often
encountered in my work was the difficulty of keeping up to date with public
filings at the Work Environment Authority, which was information that was
crucial to our campaign to secure a collective bargaining agreement.</p>

<p>666a is the tool I was missing. It’s finally here.</p>

<p>If you have any questions, feedback, or thoughts on how the tool can be further
developed, email me at henry@catalinismith.se.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="666a" />
      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[This was originally posted at https://666a.se/news/launch-announcement and has been migrated here since for long-term hosting.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Almedalsveckan 2023</title>
      <link href="https://henry.catalinismith.se/2023/06/30/almedalsveckan-2023/" rel="alternate" type="text/html" title="Almedalsveckan 2023" />
      <published>2023-06-30T00:00:00+02:00</published>
      <updated>2023-06-30T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2023/06/30/almedalsveckan-2023</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2023/06/30/almedalsveckan-2023/"><![CDATA[<figure>
 <img src="/2023/06/30/almedalsveckan-2023@1205x1205.webp" alt="
    Person stood smiling at a booth at a conference. Next to them is a banner
    for the Swedish trade union “Unionen” with a slogan encouraging people to
    organise unions at their own workplaces.
  " />
</figure>

<p>A happy photo from a magical day in Visby.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[A happy photo from a magical day in Visby.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2023/06/30/almedalsveckan-2023@1205x1205.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2023/06/30/almedalsveckan-2023@1205x1205.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Negotiation Petition Sent</title>
      <link href="https://henry.catalinismith.se/2023/04/12/negotiation-petition-sent/" rel="alternate" type="text/html" title="Negotiation Petition Sent" />
      <published>2023-04-12T00:00:00+02:00</published>
      <updated>2023-04-12T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2023/04/12/negotiation-petition-sent</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2023/04/12/negotiation-petition-sent/"><![CDATA[<video controls="" preload="none" poster="/2023/04/12/negotiation-petition-sent@1024x1024.webp">
 <source src="/2023/04/12/negotiation-petition-sent@512x512.mp4" type="video/mp4" />
</video>

<p>Made this audiogram for a Spotify Workers podcast episode using the Web Audio
API. The code is viewable at
<a href="https://glitch.com/edit/#!/negotiation-petition-sent">glitch.com/edit/#!/negotiation-petition-sent</a>,
and runnable at
<a href="https://negotiation-petition-sent.glitch.me/">negotiation-petition-sent.glitch.me</a>.</p>

<p>The episode itself is still available on Spotify.</p>

<iframe style="border-radius:12px; width: 100%;" src="https://open.spotify.com/embed/episode/5mQ1vBhdc6uygikLbR4B30?utm_source=generator" height="152" frameborder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[Big milestone in the campaign.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2023/04/12/negotiation-petition-sent@1024x1024.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2023/04/12/negotiation-petition-sent@1024x1024.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Malmö Live</title>
      <link href="https://henry.catalinismith.se/2023/02/25/malmo-live/" rel="alternate" type="text/html" title="Malmö Live" />
      <published>2023-02-25T00:00:00+01:00</published>
      <updated>2023-02-25T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2023/02/25/malmo-live</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2023/02/25/malmo-live/"><![CDATA[<figure>
 <img src="/2023/02/25/malmo-live@800x800.webp" alt="Person with long blonde hair in a bright red hoodie stands in modern-looking hotel lobby." />
</figure>

<p>Photo by <a href="https://www.freddybillqvist.com/">Freddy Billqvist</a>.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2023/02/25/malmo-live@800x800.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2023/02/25/malmo-live@800x800.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Spotify Workers Logo</title>
      <link href="https://henry.catalinismith.se/2023/01/21/spotify-workers-logo/" rel="alternate" type="text/html" title="Spotify Workers Logo" />
      <published>2023-01-21T00:00:00+01:00</published>
      <updated>2023-01-21T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2023/01/21/spotify-workers-logo</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2023/01/21/spotify-workers-logo/"><![CDATA[<figure>
 <img src="/2023/01/21/spotify-workers@1509x1509.webp" alt="A shuffle symbol with a white foreground and a red spraypaint outline is seen affixed as a sticker to a laptop next to other stickers." />
</figure>

<p>The shuffle symbol represents radical change and the creation of a new order.
The main factor in the design of this was to avoid mimicking the employer’s
existing branding in any way. This was partly a symbolic gesture as I think
unions should seek to define themselves in their own terms, and partly a legal
risk management move to avoid
<a href="https://fortune.com/2023/10/19/starbucks-sues-union-name-logo-a-pro-palestine-post-damage-reputation/">getting sued for trademark infringement</a>.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[Little bit of graphic design work I did for the Spotify campaign.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2023/01/21/spotify-workers@1509x1509.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2023/01/21/spotify-workers@1509x1509.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">My CPWA Experience</title>
      <link href="https://henry.catalinismith.se/2023/01/14/my-cpwa-experience/" rel="alternate" type="text/html" title="My CPWA Experience" />
      <published>2023-01-14T00:00:00+01:00</published>
      <updated>2023-01-14T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2023/01/14/my-cpwa-experience</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2023/01/14/my-cpwa-experience/"><![CDATA[<p>2022 was the year my dream of getting more into accessibility finally came true.
While I was studying for the
<a href="https://www.accessibilityassociation.org/s/certification">IAAP certifications</a>
I was surprised by how valuable I found other people’s accounts of how they
studied for them. I promised myself I’d contribute back to that if I passed the
exams. And I passed, so here’s what I did!</p>

<h2 id="background">Background</h2>

<p>Most people writing about accessibility online seem to be hardcore experts with
years upon years of experience working exclusively on accessibility. That’s not
me, and I think that’s probably important information here. Like, this
certification isn’t just for those superstars. So here’s some background in case
it helps someone make a decision about taking these exams.</p>

<p>At my previous job one of my side projects was scoping out and building a
business case for a WCAG compliance retrofitting project. My accessibility
knowledge back then was within the average range for a decent web developer: I
knew how to fire up VoiceOver and how to fix the worst &amp; simplest issues. I knew
I wanted to learn more, and that our app’s accessibility issues had commercial
drawbacks, but I’d never done anything like build an
<a href="https://www.w3.org/WAI/ARIA/apg/example-index/combobox/combobox-select-only.html">accessible ARIA combobox implementation</a>
myself yet.</p>

<p>The COVID-19 pandemic spoiled my plans there, because in the healthtech business
anything that wasn’t COVID-related got deprioritised in 2020. But at my next
job, COVID had the opposite effect, as the rise of remote learning brought
<em>that</em> app under the jurisdiction of
<a href="https://www.section508.gov">government procurement legislation</a>, with all its
accessibility requirements.</p>

<p>So I spent most of 2022 retrofitting a decade’s worth of accessibility issues as
quickly as I possibly could, and learned a whole lot in a very short period of
time. This was a well-funded project where I got to learn from expert colleagues
as well as third party auditors and UX research. Towards the end of this project
was when I decided to shoot my shot for the IAAP certs.</p>

<h2 id="studying">Studying</h2>

<p>The
<a href="https://dequeuniversity.com/curriculum/packages/iaap-certification-prep">Deque study package for the CPACC and WAS exams</a>
is some of the best $170 I’ve ever spent. If you’re thinking of having a go at
these exams, and if you take away nothing else from this post, you should at
least buy that package. The three most important things for me in my studies
were:</p>

<ul>
  <li>The deque course.</li>
  <li>Notebooks.</li>
  <li>Flash cards.</li>
</ul>

<p>I went through the Deque material for both exams once very slowly, making my own
set of notes in my own words. This was the part of the process where I’d stop
for more research if I found something I didn’t immediately understand. It
filled a good few notebooks by the time it was done. Once the notebook phase was
done and I was confident I’d seen and understood the full breadth of the
material, I created flash cards based on the notes.</p>

<p><img src="/2023/01/14/my-cpwa-experience-notebooks@1024x768.webp" alt="Two A5 spiral-bound notebooks on a wooden table next to a stack of flash cards about 15cm high. " /></p>

<p>My full study material for both courses looked like this once complete.</p>

<h2 id="cpacc">CPACC</h2>

<p>The CPACC course had the most material that was totally new to me, so I focused
most of my study efforts on it. The international laws, the different models of
disability, the Universal Design for Learning stuff: all of it was new and
exciting (and a bit intimidating too) so I threw most of my weight behind
learning it.</p>

<p><img src="/2023/01/14/my-cpwa-experience-economic-model@1024x768.webp" alt="
Spiral bound notebook open to a page titled 'Economic model'.
Below the title, handwritten text as follows.
Defines disability in terms of a persons inability to participate in work.
Assesses severity in terms of the degree of lost productivity and the economic consequences to individuals, employers and the state." /></p>

<p>As someone who dropped out of a humanities university degree course back in 2006
it felt great to see that I’ve learned how to engage with this kind of material
properly in the years since.</p>

<p>It was great fun learning about the different
<a href="https://en.wikipedia.org/wiki/Social_model_of_disability">models of disability</a>.
That’s such a useful conceptual framework for making sense of so many things.
<a href="https://en.wikipedia.org/wiki/Universal_Design_for_Learning">Universal Design for Learning</a>
was a topic I expected to find a little irrelevant to my own interests, but
ended up finding super fun too. The way it categorises the different aspects of
learning helped me understand my own learning process better. And the
<a href="https://ec.europa.eu/social/main.jsp?catId=1202">international legal material</a>
felt like a bit of a slog at first, but the value of the knowledge felt really
clear once it was inside my head. Having a broad idea of what the legal
requirements are like in different countries across the world gives a great
high-level perspective.</p>

<p>All told, I think the material for this exam felt about equivalent to a typical
semester-long university module. The Deque course is a great complement to the
IAAP’s own
<a href="https://www.accessibilityassociation.org/resource/IAAP_CPACC_BOK_March2020">CPACC Book of Knowledge</a>.
I don’t believe there was a single exam question that wasn’t covered by the
Deque course. They have really created a very special resource in that course.
And no, they’re not paying me to promote it.</p>

<p>If you’re interested in accessibility, I think you should definitely consider
studying for the CPACC exam. The material isn’t particularly technical as it’s
not aimed solely at software engineers. If you’re excited about accessibility
and willing to study, you can absolutely pass this exam.</p>

<p>This exam went quite well for me, which reflects how much effort I put in, I
think. I was really happy with my result, which built my confidence up for the
much harder Web Accessibility Specialist exam.</p>

<h2 id="was">WAS</h2>

<p>The study material for the WAS exam felt more familiar to me than the CPACC
stuff because it‘s more technical, which led me to invest less of my study time
in it. I regret this now. This exam turned out to be the harder of the two, and
I didn’t pass it by as big a margin as the CPACC exam.</p>

<p>I also went off on more tangents, investing a lot of time studying
<a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA">ARIA</a> and
<a href="https://www.w3.org/WAI/standards-guidelines/atag/">ATAG</a> in way more detail
than either the Deque course or the
<a href="https://www.accessibilityassociation.org/resource/WAS_Certification_FInal_2020_FINAL">WAS Book of Knowledge</a>
cover them in. I even made an honest attempt to memorise the whole
<a href="https://www.w3.org/TR/wai-aria/img/rdf_model">ARIA taxonomy</a>, which was way too
ambitious and miles beyond the scope of the Book of Knowledge.</p>

<p><img src="/2023/01/14/my-cpwa-experience-aria-taxonomy@1904x811.webp" alt="Sprawling UML diagram showing all the ARIA roles as boxes and the inheritance relationships between them as connecting lines." /></p>

<p>I tried to memorise this.</p>

<p>I’m still happy I did this because that’s knowledge I now personally value.
Having a bit of a general idea about how the different ARIA roles inherit from
other more general ones feels cool as fuck. But in hindsight I should probably
have focused more on the core material.</p>

<p>What was fun about this course was that it took me outside the stuff I’d covered
in the retrofitting project at work. One of the coolest things I was most
excited to learn about was
<a href="https://webaim.org/blog/three-things-voiceover/">the different screen reader modes in JAWS and NVDA</a>.
Since I only have access to VoiceOver, this course was my first exposure to that
concept, and it’s really important to know about!</p>

<p><img src="/2023/01/14/my-cpwa-experience-screen-reader-modes@1024x768.webp" alt="
Spiral bound notebook open to a page titled 'Screen reader modes'.
Below the title, handwritten text as follows.
Forms mode.
No char key shortcuts here b/c typing!
Focus only on focusable items so random text will be missed.
Document/Browse/Scan mode (default)
Lots of shortcuts, focus anything." /></p>

<p>This is by far the most important UX difference between VoiceOver and the big
two Windows screen readers.</p>

<p>This certification does not fuck around. It’s not like those softball LinkedIn
skill tests, and it’s sure as hell not like those scrum master “certifications”
that you get after doing a quick two day course with a perfunctory little test
on the end. To stand a chance at passing this one, you need to be willing to try
to memorise
<a href="https://dequeuniversity.com/screenreaders/nvda-keyboard-shortcuts">keyboard shortcuts for a bunch of different screen readers</a>.
You need to at least have an honest try at memorising as many of the
<a href="https://www.w3.org/TR/WCAG21/">WCAG success criteria</a> as you can. It would have
been impossible to pass this exam without the benefit of everything I learned
from the accessibility retrofitting project at work. My experience level within
the field of accessibility was definitely at the bottom end of the viable range
for this certification and I only squeezed through by studying my ass off to
compensate for that.</p>

<p>My result for this exam was too close for comfort. If I’d been a bit unlucky on
a handful of questions, I’d have failed. A pass is a pass, sure, but I’d have
preferred a comfortable pass for both exams rather than a slam dunk for one and
a near miss for the other. Something to think about if your experience profile
matches mine and you’re wondering how to structure your studies, I reckon.</p>

<h2 id="the-exams-themselves">The exams themselves</h2>

<p>Kryterion are doing a great job. The potential for cheating is obvious when
people are taking exams from home, and the risk that that represents for the
credibility of these certifications is equally obvious. After going through the
process twice, I came away with a really strong sense that successfully cheating
at these exams would have been harder than just studying for them. The Kryterion
folks know what they’re doing, and that’s great, I think.</p>

<p>That said, I’ve also heard anecdotally that this same online proctoring process
can prevent blind people from obtaining these accessibility certifications,
because it hasn’t been built in an accessible way. The irony would genuinely be
quite funny if this wasn’t having a material negative impact on peoples lives. I
hate that this is an issue. Everything else about this process has been so
fantastic, but this does piss on the cornflakes quite a bit and that’s a shame.</p>

<p>My approach to the exams was to charge my laptop beforehand and set it up on a
completely empty desk in a room with the door locked. I made sure to book the
exam for a time when it’d definitely be quiet at home too. I was a little
nervous about making the proctoring people suspicious, so taking steps like
these was a good way to bring my nerves down for the exam.</p>

<h2 id="next-steps">Next Steps</h2>

<p>My CPWA certification expires in 2025. If I want to renew it, I’ll have to earn
at least 55 “Continuing Accessibility Education Credits”. So yet again the IAAP
is setting its certifications apart from the more dubious tech industry certs
where simply paying a fee is enough to renew them. Good for them.</p>

<p>I really hope I’ll manage to squeeze that continuous learning in and hang on to
the cert. Accessibility has shown me that there’s some genuine potential for
social good inherent to literally all software. If you wanted to read a whole
bunch of different stuff from all over the world 50 years ago you were basically
just shit out of luck if you were blind, for example, and technology has
transformed that in the years since. It’s healed a badly wounded part of my soul
to find a niche in this industry where the work and its outcomes are coherent
with my own personal values.</p>

<p>The downside with accessibility seems to be that it’s a more precarious form of
work than the industry standard AB testing grind. Elon Musk’s firing of the
entire accessibility team at Twitter was shortly followed by the cancellation of
the accessibility program I’d been expecting to work on at my own job. So
further specialisation in accessibility doesn’t seem like the best option from a
pure risk minimisation perspective, but I don’t think I care. Hopefully there
will be lots of opportunities ahead to put all this hard-earned knowledge to
good use!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="accessibility" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[2022 was the year my dream of getting more into accessibility finally came true. While I was studying for the IAAP certifications I was surprised by how valuable I found other people’s accounts of how they studied for them. I promised myself I’d contribute back to that if I passed the exams. And I passed, so here’s what I did!]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2023/01/14/my-cpwa-experience-notebooks@1024x768.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2023/01/14/my-cpwa-experience-notebooks@1024x768.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Use Your Hammer, Shithouse</title>
      <link href="https://henry.catalinismith.se/2023/01/12/use-your-hammer-shithouse/" rel="alternate" type="text/html" title="Use Your Hammer, Shithouse" />
      <published>2023-01-12T00:00:00+01:00</published>
      <updated>2023-01-12T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2023/01/12/use-your-hammer-shithouse</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2023/01/12/use-your-hammer-shithouse/"><![CDATA[<video controls="" preload="none" poster="/2023/01/12/use-your-hammer-shithouse@512x449.webp">
 <source src="/2023/01/12/use-your-hammer-shithouse@512x446.mp4" type="video/mp4" />
</video>

<p>Ever since
<a href="https://www.facebook.com/share/v/E1kn5V8Dip6Zn8ED/?mibextid=KsPBc6">Darren Gee’s run-in with the hammer weirdo</a>
I find myself saying this whenever I come across Hammer Bros in Mario. And now
thanks to this video, so will you!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="video" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2023/01/12/use-your-hammer-shithouse@512x449.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2023/01/12/use-your-hammer-shithouse@512x449.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Unions Are For Us</title>
      <link href="https://henry.catalinismith.se/2022/11/28/unions-are-for-us/" rel="alternate" type="text/html" title="Unions Are For Us" />
      <published>2022-11-28T00:00:00+01:00</published>
      <updated>2022-11-28T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2022/11/28/unions-are-for-us</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2022/11/28/unions-are-for-us/"><![CDATA[<p>Many of us have grown up in fiercely anti-union cultural environments. These
early experiences guide our beliefs about the role of unions in society
throughout our lives. It’s time for us to understand this and re-examine those
beliefs with a critical perspective.</p>

<h2 id="so-long-dental-plan">So long dental plan</h2>

<p>For many of us, our first encounter with the concept of a union was <em>Last Exit
To Springfield</em>. This is rightly one of the most beloved classic Simpsons
episodes ever made. It’s a great story and it’s packed with big laughs. It’s
also brimming with anti-union messages. Here’s a clip.</p>

<figure>
 <video poster="/2022/11/28/unions-are-for-us-meeting-poster@480x360.webp" src="/2022/11/28/unions-are-for-us-meeting@480x360.mp4" title="" controls="" preload="none" playsinline="">
 </video>
 <figcaption>
 </figcaption>
</figure>

<p>In less than 30 seconds it portrays the union as corrupt, violent, feckless,
stupid and ineffective. Even the eventual victory is a skin-of-the-teeth
retention of the status quo that’s only secured through luck. It’s ahistorical
and wrong. It certainly doesn’t reflect lived experiences of workers in Sweden
where union membership has been in the 65% - 80% ballpark for decades, powering
world-leading productivity and happiness simultaneously.</p>

<p>We grew up watching this stuff, and our employers did too. Those experiences
shape our beliefs just as they shape those of our employers. We’re accustomed to
seeing our employers as authority figures and as domain experts in their field
of business, so when they reflect those same early cultural influences back at
us during the early stages of unionisation it can be very demoralising.</p>

<p>It’s critical that we’re consciously aware during those conversations that our
employers’ domain expertise ends at the boundaries of their field of business.
Employers are generally not amateur historians of the labour movement. We can
and should feel a strong sense of empathy with them when they share their
worries about a change they’ve been taught since birth to fear.</p>

<p>What we don’t owe them is to assign a false sense of authority to the things
they say about this topic. We can listen, empathise, then push it from our minds
and form our own opinion based on the observable reality around us. For those of
us in Sweden, that reality is consistently high quality of life rankings and
over two thirds union membership.</p>

<h2 id="stories-that-dont-go-anywhere">Stories that don’t go anywhere</h2>

<p>In my very first job after graduating from university we got to leave early on
Friday afternoons, and let me tell you, it was fucking <em>fantastic</em>. Half of us
went directly from the office to the nearest pub where we’d stay until we were
almost too drunk to stand.</p>

<figure>
 <img alt="Lenny drinking his beer after saying 'So long dental plan'" src="/2022/11/28/unions-are-for-us-stories-beer@480x360.webp" />
</figure>

<p>If we’d made moves towards unionising there, they’d have undoubtedly pointed to
those Friday afternoons as something that may only have been possible thanks to
the flexibility of their carte blanche authority over the terms and conditions
of work.</p>

<p>Thing is, the employers in this case benefited from that policy too. They loved
leaving early on Friday, and on more than one occasion they even followed us to
that pub. Under a collective bargaining agreement they’d have had every right to
propose that policy during the negotiation period. But they might not have known
that, and their cultural biases would have filled in that knowledge gap with
anxiety. Then they’d have presented that anxiety to us as if it was an
authoritative warning.</p>

<p>And as fun as it was getting blackout drunk by 5pm on Fridays with the bosses,
if we’d had a proper channel of communication for negotiating about the terms
and conditions of work, we might have suggested that a pension scheme was a more
urgent need. Employers and workers tend to have quite different incentives and
experiences like that. And if workers don’t talk among themselves and organise a
coherent, unified negotiation, we leave employers to fill in that gap with
guesses informed by their vastly different lives.</p>

<p>You can like your boss on a personal level and also believe you understand your
own needs better than they do. You can feel gratitude for their generosity while
acknowledging it could be harnessed more effectively through proper negotiation.</p>

<p>We have similarly generous terms governing working time at my current job. And
just like at my first job, our employers here love those policies too and
benefit greatly from them. It would be understandable for them to issue a few
anxiety-driven “warnings” in the coming months and we should be mindful to
correctly interpret those as well-meaning but misguided.</p>

<p>The decision making process about the terms and conditions of work will be
strengthened - not weakened - by the meaningful inclusion of workers in those
decisions. If you’ve experienced the guilt-laden cognitive dissonance of knowing
that your compensation is good but simultaneously wishing it was <em>different</em>,
what’s missing from your life is collective bargaining. Without collective
bargaining the amount of negotiating flexibility available to <em>you</em> is
approximately zero. Don’t allow yourself to be demoralised by these
“flexibility” and “innovation” stories that don’t go anywhere.</p>

<figure>
 <video poster="/2022/11/28/unions-are-for-us-stories-poster@480x360.webp" src="/2022/11/28/unions-are-for-us-stories@480x360.mp4" title="" controls="" preload="none" playsinline="">
 </video>
 <figcaption>
 </figcaption>
</figure>

<p>Imagine a business transaction. Maybe a CEO is negotiating new terms with an
important supplier. The supplier offers to handle both sides of the negotiation
and promises the CEO better terms in exchange for this concession. Patently
ridiculous, right? This is the offer we’re accepting by choosing not to
negotiate collectively on our own behalf. Even if the CEO’s lifelong best friend
is in charge at the supplier, it’s an offer they’d refuse anyway as it’d set a
precedent that would be difficult to change later if either of them moved to a
new company.</p>

<h2 id="it-was-the-best-of-times-it-was-the-blurst-of-times">It was the best of times, it was the blurst of times</h2>

<p>Speaking of CEOs leaving companies, today is exactly one year since November
28th 2021, which was the last ever normal Sunday for the employees of Twitter.
As far as they knew, everything was wonderful and would be wonderful on Monday
too. If you’d asked them about unionising that weekend, they might have
suggested that they didn’t need to because things were so good. The next day,
Jack Dorsey resigned, setting in motion a chain of events leading to mass
layoffs and significantly worse working conditions for those left behind.</p>

<figure>
 <video poster="/2022/11/28/unions-are-for-us-blurst-poster@480x360.webp" src="/2022/11/28/unions-are-for-us-blurst@480x360.mp4" title="" controls="" preload="none" playsinline="">
 </video>
 <figcaption>
  "please be prepared to do brief code reviews as I'm walking around the office."
 </figcaption>
</figure>

<p>Would an identical company with a strong union have been such an attractive
hostile takeover target for a CEO with those intentions? Could that union have
influenced the outcome of the takeover bid through industrial action? Might they
have been able to offer some resistance to the onslaught of ultimatums and
dictates regarding working conditions? Perhaps we can make an educated guess
based on
<a href="https://www.cnbc.com/2022/08/29/tesla-ban-on-pro-union-shirts-violated-workers-rights-nlrb.html">Elon Musk’s long established opposition to unions</a>.</p>

<figure>
 <img alt="Mr. Burns and his father in a sepia-filtered scene where they confront a worker." src="/2022/11/28/unions-are-for-us-atoms@480x360.webp" />
 <figcaption>
  Elon and his dad greet one of the many happy employees at their Zambian emerald mine.
 </figcaption>
</figure>

<p>The best time to join a union is when things are good. To do so is to recognise
that things are good, and that it’s worth taking steps to protect what you and
your employer have built together. At its best, signing a collective agreement
is an act of deep mutual respect.</p>

<p>The worst time to join a union is when it’s too late to make a difference
because you waited for a crisis. In Jane McAlevey’s <em>A Collective Bargain</em>, she
refers to this scenario as a “hot shop”.</p>

<blockquote>
  <p>The employer did something horribly wrong, which enraged a majority of workers
pretty much overnight, and they rushed into a drive to organize. Most hot shop
efforts in our current climate end up similar to Winmore’s: they fail, despite
the agitation (“heat”) for a union.</p>
</blockquote>

<p>Even if they’d begun to organise on November 29th, the workers at Twitter might
have been too late to stop what happened to them. From what I’ve heard it sounds
like they’d built a really special culture there, and now it’s been destroyed.
Obviously it feels quite far fetched to imagine that happening at any of the
companies where the rest of us work. It would have seemed far fetched at Twitter
too.</p>

<p>The idea that unionisation is primarily a crisis response tactic is a widespread
cultural trope. It’s incorrect. Unions make good companies better.</p>

<h2 id="they-have-the-plant-but-we-have-the-power">They have the plant but we have the power</h2>

<p>Will you work at your current job for the rest of your career? Are you sure? How
sure? As sure as those engineers at Twitter were a year ago?</p>

<p>If there’s a particular innovative policy that you like at your current job,
then the rational thing for you to do is to use the power available to you via
the labour movement to help that policy spread throughout society at large. In
Sweden there’s a well-defined process for this. For next year, they’re
prioritising a
<a href="https://www.unionen.se/opinion/avtal-2023-detta-vill-vi-i-avtalsrorelsen">higher-than-normal wage increase</a>
and further expansion of the flexible pension system.</p>

<p>Offering unique one-of-a-kind benefits is in your employer’s interest because
scarcity increases their power in their relationship with you. Improving the
terms and conditions of work in general across society is in your personal
interest as a worker because it decouples your basic quality of life from the
randomness of the financial markets. This is the mindset that keeps Sweden at
the top end of those “happiest country in the world” lists.</p>

<p>Is it possible you may have conflated your employer’s interests with your own?
Do you want a unique one-of-a-kind dental plan that vanishes from your life
forever when you’re laid off in the next financial crisis, or do you want that
same dental plan to be table stakes for all employers so that you can keep it in
your next job?</p>

<figure>
 <img alt="Lisa's school photo with her cheap braces resulting from her father's lack of a dental plan." src="/2022/11/28/unions-are-for-us-photo@480x360.webp" />
</figure>

<p>The idea that unions are primarily a tool for workers with weaker leverage at
the lower end of the compensation scale is culturally widespread. It’s a key
reason behind the low union membership in the tech industry. It’s incorrect. The
basic laws of physics are the same for all workers regardless of how many ping
pong tables are at their employer’s headquarters.</p>

<p>There’s no amount of individual brilliance and luck that can overcome global
macroeconomic forces. Even those world-class engineers who’ve taken the time to
master that whole LeetCode interview rat race and gotten into Facebook are being
laid off in their thousands now.</p>

<p>If you’re a software engineer wanting to secure your current quality of life for
the future, you could grind LeetCode for a few months and passively hope you
come out of the recession lucky. Or you can do something with some actual impact
and <a href="https://www.unionen.se/en/become-member"><strong>join a union now</strong></a>.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[Yes even us]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2022/11/28/unions-are-for-us-meeting-poster@480x360.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2022/11/28/unions-are-for-us-meeting-poster@480x360.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Squad, Tribe, Union</title>
      <link href="https://henry.catalinismith.se/2022/11/25/squad-tribe-union/" rel="alternate" type="text/html" title="Squad, Tribe, Union" />
      <published>2022-11-25T00:00:00+01:00</published>
      <updated>2022-11-25T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2022/11/25/squad-tribe-union</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2022/11/25/squad-tribe-union/"><![CDATA[<p>In the 2010s, a new Swedish way of organising product development work took the
tech industry by storm. Henrik
Kniberg’s <a href="https://blog.crisp.se/wp-content/uploads/2012/11/SpotifyScaling.pdf">whitepaper about scaling agile</a> –
full of hip new words like “squads” – was the defining management fad of the
decade. If you’re in tech, you probably have fond memories of a day some five or
six years ago when you found out you and your colleagues were soon to be
re-organised according to this model.</p>

<p>Some of us were so impressed that we were inspired to emigrate - leaving behind
our families, friends, and favourite burrito places - and go to work for the
company where the model originated. Many of us were astonished by the contrast
between Sweden and the countries we’d left behind. World-leading union
membership levels had won a level of dignity for workers here that was
unimaginable back home. Six months of parental leave on full pay is commonplace
here thanks to unions.</p>

<p>We didn’t have a collective agreement of our own back then. We didn’t much care
either. We were more excited about that world famous agile methodology. And
besides, we still benefited from the herd immunity of all the other Swedish
companies’ collective agreements, since matching those collectively bargained
compensation packages was a prerequisite of competing for talent in Sweden.</p>

<p>The 2010s are behind us now, and the 2020s are shaping up to be quite a
different kind of decade. One thing I want to carry forward, though, is Sweden
being an exporter of cool, progressive ways to improve organisations. For the
2020s, Sweden’s 70% nationwide union membership is what the world should be
copying.</p>

<p>To explain why, I want to share this modern reimagining of that infamous 2012
scaling agile whitepaper, and try to bridge the union knowledge gap using ideas
we’re already familiar with in tech. Just like the whitepaper was back then,
this is somewhat aspirational. My colleagues and I haven’t achieved our
collective agreement yet. This is the way for us to help keep Sweden at the
vanguard of the tech industry, and give our employer a chance to enjoy another
ten years as the most exciting, relevant and influential employer brand.</p>

<h2 id="command-and-control">Command and control</h2>

<p>We need to lay down some foundations first to create some perspective for the
model itself, so let’s begin with a completely blank slate. Here’s a
hypothetical company with 24 employees.</p>

<p><img src="/2022/11/25/squad-tribe-union-individual-structure@768x265.webp" alt="24 faceless blue humanoid shapes arranged in three rows and two columns." /></p>

<p>The individual worker is the basic indivisible unit of this org chart. Each one
sells their labour power to the employer in exchange for a salary.</p>

<p>The most simplistic organisational model is direct command and control, where
the employer assigns tasks to each individual worker one by one. Here’s an
illustration that visualises the employer’s line of communication with the
workers under the command and control model.</p>

<p><img src="/2022/11/25/squad-tribe-union-individual-organisation@768x556.webp" alt="
Large red circle with a symbol of a building inside it.
Dozens of multicolored arrows shooting out of the circle.
Each arrow connects it to one of the 24 faceless blue humanoids below.
Visually very chaotic." /></p>

<p>Looks like absolute chaos, right? Siloing each worker like this would
disincentivise collaboration and inhibit the formation of communities. People
facing similar challenges wouldn’t build relationships with each other, so
opportunities to share experiences and uncover economies of scale would be
missed. Nonexistent collaboration would lead to significant duplication of
effort. The lack of transparency would breed suspicion and resentment about
fairness. And if a worker became ill, for example, whatever tasks they were
responsible for would simply stop happening. Bottom line: you’d make less money
this way.</p>

<p>This approach to management is not trendy at all. Capital recognises that
workers achieve better outcomes through organisation. Workers agree.</p>

<h2 id="squad">Squad</h2>

<p>Let’s assume the different arrow colours in the diagram above represent
different business goals that the tasks contribute to. Under Henrik Kniberg’s
model we should get the people with the same mission together in squads and give
those squads ownership of that mission instead of assigning each individual task
from the top down. So let’s get our re-org started and set up some squads.</p>

<p><img src="/2022/11/25/squad-tribe-union-squad-structure@768x328.webp" alt="
24 faceless blue humanoids arranged in three rows and two columns.
Each set of four is enclosed in a box representing a squad." /></p>

<p>Company owners who are accustomed to the command-and-control model can find this
quite a scary transition. And it’s understandable. Superficially, it’s a lot of
power to relinquish. And all those new squads have leaders insisting on new
things like autonomy and ownership, which initially feels more like a challenge
to their central authority than a new kind of collaboration.</p>

<p>The results are impossible to argue with though. The increased engagement alone
is worth equivalent to a doubling of the headcount. Workers pool their knowledge
and discover that each individual had their own unique workflows and
workarounds. The best workflows spread now, and everything speeds up.</p>

<p><img src="/2022/11/25/squad-tribe-union-squad-organisation@768x595.webp" alt="
Large red circle with a symbol of a building inside it.
Six multicolored arrows shooting out of the circle connecting it to each of the six squads below." /></p>

<p>The company owners discover that they have a lot more free time. Under
command-and-control, workers required their constant attention. Each time a task
was completed, a new one had to be assigned immediately. This reactive work ate
up most of their time, reducing their availability for initiatives to explore
potential new sources of value.</p>

<p>In exchange for a bit of trust and autonomy, the workers are able to deliver a
significant increase in surplus value to the employer. Everyone’s happy. The
employer can grow the business, and the workers are that little bit less
alienated from their product.</p>

<p>We’re not finished yet, though. The dependencies and relationships between the
squads are important, but this org design doesn’t address them yet. If one squad
needs something from another, but they’re busy on another project, what takes
priority? How do we ensure that the squad missions are working towards a
coherent overall goal? Is that the company owner’s job?</p>

<h2 id="tribe">Tribe</h2>

<p>Continuing our re-org, let’s identify our squads with related areas and put them
in tribes accordingly. Our imaginary company has two. They’re quite small for
tribes really, but this model is all about scaling anyway so let’s assume there
are big expansion plans for both tribes.</p>

<p><img src="/2022/11/25/squad-tribe-union-tribe-structure@768x397.webp" alt="
24 faceless blue humanoids arranged in three rows and two columns.
Each set of four is enclosed in a box representing a squad.
Each column of 12 is enclosed in another box representing a tribe." /></p>

<p>This extra organisational layer maximises the strategic impact of the work of
the squads. Tribe leads have a very broad perspective of the tribe’s business
context, enabling them to identify opportunities that may not be apparent at the
lower level to product managers in squads.</p>

<p>It also further streamlines the communication between the employer and the
workers.</p>

<p><img src="/2022/11/25/squad-tribe-union-tribe-organisation@768x621.webp" alt="
Large red circle with a symbol of a building inside it.
Two multicolored arrows shooting out of the circle connecting it to both of the tribes below." /></p>

<p>The broader missions of tribes means they operate on longer timelines than
squads. This enables employers to set a long-term goal and make the tribe leads
accountable for delivering it. It deepens the strategic maturity of the
company’s operations, which are now unrecognisable compared to the
command-and-control model from the beginning.</p>

<p>The incredible popularity of this model throughout the 2010s demonstrates that
employers and workers understand that organised, autonomous groups of workers
can deliver superior outcomes as compared to siloed individuals. Some of us felt
strongly enough about the importance of this that it played a role in convincing
us to move to Sweden to be a part of it.</p>

<p>But this old model only covers the employer’s side of the business relationship.
And employers are right to focus on their side: running a successful product
development company is very difficult. Applying the lessons of this model to the
workers’ side of the relationship to meet the challenges of the new decade is
our responsibility as workers. Only we can do it.</p>

<h2 id="union">Union</h2>

<p>In much of the world, it’s still considered normal to manage the worker’s side
of the business relationship with the command-and-control model. Each worker
negotiates the terms of their employment individually with the employer, with
very little transparency. Cooperation between employees is discouraged in favour
of maximising the employer’s influence.</p>

<p><img src="/2022/11/25/squad-tribe-union-individual-organisation@768x556.webp" alt="
Large red circle with a symbol of a building inside it.
Dozens of multicolored arrows shooting out of the circle.
Each arrow connects it to one of the 24 faceless blue humanoids below.
Visually very chaotic." /></p>

<p>This has the same inferior outcomes in this context as it does for product
development.</p>

<p>The absence of a clear organisational channel to communicate about shared
challenges leads to significant duplication of effort. Without a structured way
to negotiate, workers resort to a system of ad-hoc petitions and word-of-mouth
pressure campaigns. Each time workers undertake one of these campaigns they have
to repeat time-consuming networking and promotional work.</p>

<p>For the employers, responding to these petitions is reactive work that’s
impossible to manage satisfactorily. Without a defined negotiation period,
there’s no way to get a holistic look at the workers’ full set of demands. Last
month they wanted reforms to the stock option program. This month they want to
change how the fitness stipend works.</p>

<p>Put yourself in the employer’s shoes and imagine trying to decide if this
current request is the right one to fund. You can’t fund them all. How do you
know there isn’t a more impactful and cheaper request coming next month that
you’ll regret not waiting for? The workers don’t even know that themselves yet,
because they’re completely disorganised.</p>

<p>This is a solved problem, and the solution is the Swedish collective agreement
system. Let’s re-org our workers one more time. This time, they’re joining a
union.</p>

<p><img src="/2022/11/25/squad-tribe-union-union-structure@768x332.webp" alt="
24 faceless blue humanoids arranged in three rows and two columns.
All 24 are enclosed in a single large box representing a union." /></p>

<p>This is an easy re-org compared to the squads and tribes. For one, it creates an
order of magnitude fewer new leadership roles. Also, because the workers are
very happy with their current terms, the initial collective agreement is easy to
achieve.</p>

<p>Just like the transition from the command-and-control approach to product
development, this moment is understandably a little scary on the employer’s
side. And the results are just as impossible to argue with: unions are how
Sweden so successfully balances the needs of its workers and its employers,
leading the world in quality of life and innovation simultaneously.</p>

<p>In our imaginary company, the chaos of the command-and-control model is gone,
and the lines of communication between the employer and the workers are
significantly streamlined.</p>

<p><img src="/2022/11/25/squad-tribe-union-union-organisation@768x612.webp" alt="
Large red circle with a symbol of a building inside it.
One arrow shooting out of it.
The arrow connects it to the single box below containing 24 faceless blue humanoids." /></p>

<p>The union’s ownership of its mission empowers it to develop a deep understanding
of the workers’ current likes and dislikes about their working conditions. These
can then be collected together, prioritised, and negotiated over in a structured
way each time the previous collective agreement expires.</p>

<p>The holistic perspective of the workers’ requests means the employer is more
readily able to respond to them. The commitment to peace once the agreement is
signed gives them a guarantee that there really isn’t another set of requests
just around the corner. That certainty empowers them to give workers more in
return for their labour.</p>

<h2 id="the-time-is-now">The time is now</h2>

<p>My colleagues have inspired the world before. For years after I moved to Sweden,
I would periodically hear from yet another friend back home in the UK whose
employer was adopting Henrik Kniberg’s agile model. It felt like the whole UK
was doing squads and tribes at one point.</p>

<p>Thatcherism mortally wounded the union movement in the UK, kicking off a
political crisis that’s still smouldering today. Union membership levels back
home seem to have finally hit rock bottom after decades of decline. What I
really want right now is for my colleagues in Sweden to inspire all my friends
back home one more time and kickstart their recovery.</p>

<p>If they see us do it, they might do it too. We hold tremendous power in our
hands right now. We can be part of a hugely consequential international tipping
point if we can find the will to use that power. Many of us packed up our entire
lives and moved countries to be a part of the old model. So we already know
we’re brave enough to do a
<a href="https://www.unionen.se/en/become-member">quick Bank ID signup</a> for this new
one.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[A new kind of scaling for a new kind of decade.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2022/11/25/squad-tribe-union-individual-structure@768x265.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2022/11/25/squad-tribe-union-individual-structure@768x265.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Why A Collective Agreement</title>
      <link href="https://henry.catalinismith.se/2022/11/23/why-a-collective-agreement/" rel="alternate" type="text/html" title="Why A Collective Agreement" />
      <published>2022-11-23T00:00:00+01:00</published>
      <updated>2022-11-23T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2022/11/23/why-a-collective-agreement</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2022/11/23/why-a-collective-agreement/"><![CDATA[<h2 id="for-the-past">For the past</h2>

<p>Workers in Sweden today owe their good working conditions to the country’s
strong labour movement. A century of worker power has carved high expectations
of working conditions into the bedrock of Swedish culture.</p>

<p>There exist rare cases today where workers without collective agreements receive
benefits such as paid parental leave anyway. Even then, those workers are
indirectly profiting from the strength of the labour movement which made those
benefits commonplace. Organised labour is such a driving force in Sweden that
employers still have to compete with the collectively bargained compensation
offered by their competitors even if their own employees haven’t organised yet.</p>

<p>It took decades upon decades to build what we have today in Sweden. Many
hundreds of thousands of people made untold sacrifices along the way. This is an
important and successful social project spanning multiple generations. It’s
something we should be enormously proud of and work to maintain.</p>

<h2 id="for-the-present">For the present</h2>

<p>Hard times are here. Cost-cutting and <a href="https://layoffs.fyi">layoffs</a> are
everyday things right now. I broadly trust the people I work for, but there’s no
collective agreement in place. I believe my employer’s intentions are good, but
now is no time for wishful thinking. Now is the time to stand eye-to-eye as
adults and make written, legally binding commitments.</p>

<p>I’m from a part of the world where union power was decimated by Thatcherism.
Organising is still possible in the UK and groups like <a href="https://utaw.tech">UTAW</a>
are proof of what’s possible. But the idea of a country where two thirds of the
population are union members almost sounds like fantasy to someone like me. We
have real power in our hands, right here and now. The question is whether we can
find the will to use it.</p>

<p>While we’re deciding that, a newly elected right wing government is beginning
the process of defunding key state institutions and social programs. The labour
movement in Sweden has played an important role as a hedge against the slow
unraveling of the welfare state since the 90s, and now is no exception. If you
have private health insurance, for example, now would be an especially bad time
to lose it.</p>

<h2 id="for-the-future">For the future</h2>

<p>My children will grow up in Sweden. One day they’ll need jobs. If the legacy of
the boomers is that of a kind of asshole generation, pulling up the ladder
behind themselves, I want my generation’s to be the opposite. I’ve seen
first-hand what a weak labour movement and years of right wing cuts do to a
country and I don’t want my kids to have to work on
<a href="https://en.wikipedia.org/wiki/Zero-hour_contract">zero-hour contracts</a> at
Biff’s Pleasure Paradise. I want to pay forward with interest what workers in
Sweden fought for over the course of the past century.</p>

<p>The future’s uncertain. The rise of remote work and the platform economy - both
of which are trends my employer is a key driver of - each present their own
unique challenges to the labour movement. It’s critical that we meet those
challenges head-on, and I think my colleagues and I have a key role to play in
that.</p>

<p>The company I work for is very influential in my industry. For the past decade,
<a href="https://www.atlassian.com/agile/agile-at-scale/spotify">companies have looked to us for inspiration</a>
about how to organise the wage labour of their workers. I want the decade ahead
to be about those same workers in those same companies looking to us for
inspiration about a different kind of organising. I want the “Spotify Model” of
the 2020s to be union membership.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[If you don't hear boss music playing right now, maybe you aren't listening.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">#kollektivavtal</title>
      <link href="https://henry.catalinismith.se/2022/11/14/kollektivavtal/" rel="alternate" type="text/html" title="#kollektivavtal" />
      <published>2022-11-14T00:00:00+01:00</published>
      <updated>2022-11-14T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2022/11/14/kollektivavtal</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2022/11/14/kollektivavtal/"><![CDATA[<blockquote>
  <p>This is a full archive copy of the inaugural post I wrote for the
#kollektivavtal Slack channel at Spotify. On November 14, 2022 I created this
channel and then copypasted in this pre-written text to try to start it off on
the right foot.</p>
</blockquote>

<p>It’s time to move towards getting a collective agreement in place, like the
majority of other workers in Sweden already have. Slido questions and ad-hoc
Google doc petitions have served us well over the years, but the company’s
outgrown them: there are so many of us that it’s become chaotic. We’ve become a
large company, and now’s the time to stop muddling along with a small company
approach to these conversations. Going forward, the structure and leverage of a
collective agreement is the way for us to influence how our working conditions
evolve during the uncertain times ahead.</p>

<p>I personally have a lot of confidence in the current senior leadership team and
I think they’re genuinely committed to the Swedish values of Spotify’s employer
branding. That’s the reason I feel comfortable posting this despite the stigma
that exists around this topic in other countries: I don’t anticipate any San
Francisco-style unpleasantness. That’s partly because collective agreements are
as Swedish as julmust and parental equality and therefore an opportunity to
develop the Spotify employer brand to a new level, but also because I think it’s
clear it’s the right way to scale these conversations after this year of
dizzying headcount growth.</p>

<p>In the short term, a collective agreement can also provide protection for
benefits such as paid parental leave, which are otherwise theoretically
vulnerable to being cut in favor of investments elsewhere. In the long term, it
can even provide some continuity between senior leadership teams, by limiting
how drastically a new CEO - for example - can change our working conditions.</p>

<p>I want to be clear that I don’t believe any such changes are imminent, and that
if they were, we’d be starting this process too late now anyway. But none of us
has a crystal ball with which to make guarantees about what will or won’t happen
in the future, and especially not given the current macroeconomic context. In
fact I believe this is another reason why a collective agreement could be
welcomed by both sides: our current senior leadership team are sincere about the
values underpinning the working conditions here at Spotify, and a collective
agreement takes certain options off the table that investors may otherwise
pressure them to consider if - for example - we have a bad series of quarterly
results.</p>

<p>If you’re on board with the idea. Join Unionen now if you’re not already a
member. There are other unions out there too, and membership in those also
helps, but Unionen is the biggest, most inclusive choice, and a safe bet. If
you’re in Sweden on a work permit, look into guest membership. If you have any
questions or thoughts, let’s discuss them here in this channel. I know many of
you have wanted a collective agreement for some time but are perhaps a little
nervous about it, and I hope I’ve convinced you it’s not as adversarial or
conspiratorial as you might have thought. but if not. I’m also available to talk
in private. I’m gonna get Unionen on the phone ASAP to discuss next steps, and
if there’s anything to share l’ll post it here, so be sure to join the channel
if you’re interested!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[This is a full archive copy of the inaugural post I wrote for the #kollektivavtal Slack channel at Spotify. On November 14, 2022 I created this channel and then copypasted in this pre-written text to try to start it off on the right foot.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Passport Hacking</title>
      <link href="https://henry.catalinismith.se/2022/11/09/passport-hacking/" rel="alternate" type="text/html" title="Passport Hacking" />
      <published>2022-11-09T00:00:00+01:00</published>
      <updated>2022-11-09T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2022/11/09/passport-hacking</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2022/11/09/passport-hacking/"><![CDATA[<p>The passport system in Sweden collapsed earlier this year when pandemic travel
restrictions lifted and half the country tried to renew their passports at once.
In March I tried to book an appointment to get passports for the kids so they
could visit their extended family for the first time. The date I got was in
October.</p>

<figure>
 <img src="/2022/11/09/passport-hacking-passexpedition@768x640.webp" alt="Waiting room with fluorescent lights and an LED sign showing current queue positions. Wooden benches in the foreground. Behind them are several booths with curtains around them for taking flash photos inside." />
 <figcaption>
  In Sweden you have to go to one of these special offices where a cop literally watches you pose for your passport photo.
 </figcaption>
</figure>

<h2 id="pass-für-alle">Pass für alle</h2>

<p>By May the crisis was so bad that
<a href="https://github.com/jonkpirateboy/Pass-fur-alle">Pass für alle</a> went viral. This
was a bit of JavaScript that could automatically refresh the passport booking
page and grab earlier slots.</p>

<p>It helped a few Stockholm people I knew get a better appointment, but it wasn’t
working for me down in Skåne. In Stockholm there was more of everything: more
passport offices, more cancellations, more new booking slots available. In Skåne
there was less activity, so the script needed to run longer to get results. And
there’s only one passport office per town here, so the logistics are more
complicated.</p>

<p>I <em>really</em> wanted to get the kids to the UK during the summer if possible so
that we wouldn’t spend the whole trip indoors. So I set about building my own
Skåne-friendly version of Pass für alle.</p>

<h2 id="beep-boop">Beep boop</h2>

<p>The first thing I did was make it beep. Skåne’s slower pace meant it wasn’t
practical to sit and watch the screen. The beeping meant the laptop could be
across the room and I’d know how it was going without looking.</p>

<h3 id="beep-beep">Beep beep</h3>

<p>First up: some constant high pitched beeping.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>setInterval(() =&gt; {
  sfx(pow2(9), "triangle", pow2(8))
}, pow2(10))
setInterval(() =&gt; {
  sfx(pow2(9), "triangle", pow2(8))
}, pow2(11))
</code></pre></div></div>

<figure>
 <audio controls="" src="/2022/11/09/passport-hacking-beep-beep.mp3"></audio>
 <figcaption>
  Beep beep
 </figcaption>
</figure>

<p>That made for a nice loud heartbeat so that the sudden silence would be
noticeable if anything broke and it stopped running. It’s quite a high-pitched
and annoying sound, but it had to compete with the noise from two small kids.</p>

<h3 id="boop-boooooop">Boop boooooop</h3>

<p>The most common thing to happen after reloading the page was for no appointments
to be found. This needed a noise too.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sfx(20, "square", 500)
  .then(() =&gt; wait(10))
  .then(() =&gt; sfx(40, "square", 4000))
</code></pre></div></div>

<figure>
 <audio controls="" src="/2022/11/09/passport-hacking-boop-boooop.mp3"></audio>
 <figcaption>
  Boop booooop.
 </figcaption>
</figure>

<p>I kept the pitch low for this one. The script failed like 99.99% of the time so
this didn’t need a lot of attention. Plus from a musical perspective it combined
quite nicely with the high pitch of the page load heartbeat like this.</p>

<h3 id="beep-beep-boop">Beep beep boop</h3>

<p>Sometimes there would be an appointment a few towns away. As long as it was late
enough to get there in time by train this was great news! A nice high pitched
alert sound announced this scenario.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sfx(600, "sawtooth", 400)
  .then(() =&gt; wait(10))
  .then(() =&gt; sfx(400, "sawtooth", 420))
  .then(() =&gt; wait(10))
  .then(() =&gt; sfx(200, "sawtooth", 440))
</code></pre></div></div>

<figure>
 <audio controls="" src="/2022/11/09/passport-hacking-beep-beep-boop.mp3"></audio>
 <figcaption>
  Beep beep boop.
 </figcaption>
</figure>

<h3 id="boop-boop-beep">Boop boop beep</h3>

<p>The best case scenario was an appointment here in Malmö. A triumphant little
chirp was the sound for this.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sfx(400, "triangle", 500)
  .then(() =&gt; wait(10))
  .then(() =&gt; sfx(500, "triangle", 220))
  .then(() =&gt; wait(10))
  .then(() =&gt; sfx(600, "triangle", 240))
</code></pre></div></div>

<figure>
 <audio controls="" src="/2022/11/09/passport-hacking-boop-boop-beep.mp3"></audio>
 <figcaption>
  Boop boop beep!
 </figcaption>
</figure>

<h2 id="the-finished-product">The finished product</h2>

<p>If you’re interested, you can read
<a href="/2022/11/09/passport-hacking-script.js">the full source code</a>. For a few days I
ran that for several hours at a time. Here’s a little ten second taste of that
experience.</p>

<figure>
 <video poster="/2022/11/09/passport-hacking-running-poster@768x432.webp" src="/2022/11/09/passport-hacking-running@768x432.mp4" title="Macbook with the code linked above visible on screen and an excited baby repeatedly hitting the keyboard with his hand." controls="" preload="none" playsinline="">
 </video>
 <figcaption>
  The secret ingredient is violence.
 </figcaption>
</figure>

<h2 id="climactic-ending">Climactic Ending</h2>

<p>Around about the same time as I got this running, the police increased their
capacity and added a bunch of new times. The instant they did that I got a
next-day booking the old fashioned way. lol.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[The passport system in Sweden collapsed earlier this year when pandemic travel restrictions lifted and half the country tried to renew their passports at once. In March I tried to book an appointment to get passports for the kids so they could visit their extended family for the first time. The date I got was in October.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2022/11/09/passport-hacking-running-poster@768x432.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2022/11/09/passport-hacking-running-poster@768x432.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Tailbone Instructions</title>
      <link href="https://henry.catalinismith.se/2022/10/25/tailbone-instructions/" rel="alternate" type="text/html" title="Tailbone Instructions" />
      <published>2022-10-25T00:00:00+02:00</published>
      <updated>2022-10-25T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2022/10/25/tailbone-instructions</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2022/10/25/tailbone-instructions/"><![CDATA[<h2 id="jump">Jump</h2>

<p>On a touchscreen device, press the screen once to jump. On a keyboard, the <code class="language-plaintext highlighter-rouge">X</code>
key triggers a jump. The <code class="language-plaintext highlighter-rouge">space</code> key also works. Clicking the left mouse button
works too.</p>

<figure>
 <video controls="" loop="" muted="" playsinline="" preload="none" style="aspect-ratio:1/1" poster="/2022/10/25/tailbone-jump-poster@512x512.webp" src="/2022/10/25/tailbone-jump-loop@512x512.mp4"></video>
 <figcaption>
  You die if you crash into a cactus!
 </figcaption>
</figure>

<h2 id="slam">Slam</h2>

<p>While in the air, press jump a second time to slam.</p>

<figure>
 <video controls="" loop="" muted="" playsinline="" preload="none" style="aspect-ratio:1/1" poster="/2022/10/25/tailbone-slam-poster@512x512.webp" src="/2022/10/25/tailbone-slam-loop@512x512.mp4"></video>
 <figcaption>
  Slamming a cactus kills it!
 </figcaption>
</figure>

<h2 id="fly">Fly</h2>

<p>Instead of releasing your slam straight away, hold down that second jump press
to fly. Flying makes your jump last longer so you can cross larger distances in
the air.</p>

<figure>
 <video controls="" loop="" muted="" playsinline="" preload="none" style="aspect-ratio:1/1" poster="/2022/10/25/tailbone-grab-poster@512x512.webp" src="/2022/10/25/tailbone-grab-loop@512x512.mp4"></video>
 <figcaption>
  You can fly for about one whole screenful of distance.
 </figcaption>
</figure>

<h2 id="combo">Combo</h2>

<p>Chain together multiple tricks without touching the floor to fill up your combo
bar. Each time you max out the bar you get a score bonus and the difficulty
increases slightly.</p>

<figure>
 <video controls="" muted="" playsinline="" preload="none" style="aspect-ratio:1/1" poster="/2022/10/25/tailbone-combo-poster@512x512.webp" src="/2022/10/25/tailbone-combo-loop@512x512.mp4"></video>
 <figcaption>
  Watch out for the lava!
 </figcaption>
</figure>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="gamedev" />
      
        <category term="tailbone" />
      

      
      
        <summary type="html"><![CDATA[Jump]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2022/10/25/tailbone-jump-poster@512x512.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2022/10/25/tailbone-jump-poster@512x512.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Sequencer VoiceOver Demo</title>
      <link href="https://henry.catalinismith.se/2022/04/04/sequencer-voiceover-demo/" rel="alternate" type="text/html" title="Sequencer VoiceOver Demo" />
      <published>2022-04-04T00:00:00+02:00</published>
      <updated>2022-04-04T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2022/04/04/sequencer-voiceover-demo</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2022/04/04/sequencer-voiceover-demo/"><![CDATA[<video controls="" preload="none" poster="/2022/04/04/sequencer-voiceover-demo@512x260.webp">
 <source src="/2022/04/04/sequencer-voiceover-demo@512x260.mp4" type="video/mp4" />
</video>

<p>Retrofitted this drum sequencer UI with some accessibility improvements.
Particularly proud of how the use of “bars”, “beats” and “cells” in the ARIA
labels makes it possible to perceive a data structure that was previously only
represented visually via varying amounts of blank space. I think it makes for a
lovely demo of what’s possible in this field.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="accessibility" />
      
        <category term="video" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[Retrofitted this drum sequencer UI with some accessibility improvements. Particularly proud of how the use of “bars”, “beats” and “cells” in the ARIA labels makes it possible to perceive a data structure that was previously only represented visually via varying amounts of blank space.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2022/04/04/sequencer-voiceover-demo@512x260.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2022/04/04/sequencer-voiceover-demo@512x260.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Island Village</title>
      <link href="https://henry.catalinismith.se/2021/05/21/island-village/" rel="alternate" type="text/html" title="Island Village" />
      <published>2021-05-21T00:00:00+02:00</published>
      <updated>2021-05-21T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2021/05/21/island-village</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2021/05/21/island-village/"><![CDATA[<figure>
 <img src="/2021/05/21/island-village@2732x2048.webp" alt="Minecraft screenshot featuring an aerial shot of a T-shaped island with a village on it and walls around the perimeter." />
</figure>

<p>A village transformation I did in Minecraft. This was seed 542630838 on Bedrock.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2021/05/21/island-village@2732x2048.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2021/05/21/island-village@2732x2048.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Boomshak</title>
      <link href="https://henry.catalinismith.se/2021/03/11/boomshak/" rel="alternate" type="text/html" title="Boomshak" />
      <published>2021-03-11T00:00:00+01:00</published>
      <updated>2021-03-11T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2021/03/11/boomshak</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2021/03/11/boomshak/"><![CDATA[<video controls="" preload="none" poster="/2021/03/11/boomshak@1344x1344.webp">
 <source src="/2021/03/11/boomshak@512x512.mp4" type="video/mp4" />
</video>

<p>An SVG remake I made of the PICO-8 font. The
<a href="https://github.com/henrycatalinismith/boomshak">code for this is on GitHub</a> and
there’s <a href="https://henry.catalinismith.com/boomshak/">a little demo site</a> where
you can browse the glyphs. My fav detail about this is how animateable it is, so
that’s what the video showcases.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2021/03/11/boomshak@1344x1344.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2021/03/11/boomshak@1344x1344.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">moonrise</title>
      <link href="https://henry.catalinismith.se/2021/03/04/moonrise/" rel="alternate" type="text/html" title="moonrise" />
      <published>2021-03-04T00:00:00+01:00</published>
      <updated>2021-03-04T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2021/03/04/moonrise</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2021/03/04/moonrise/"><![CDATA[<video controls="" preload="none" poster="/2021/03/04/moonrise-demo-poster@512x512.webp">
 <source src="/2021/03/04/moonrise-demo@512x512.mp4" type="video/mp4" />
</video>

<p>Moon animation in pure CSS.</p>

<iframe height="300" style="width: 100%;" scrolling="no" title="Moonrise" src="https://codepen.io/henrycatalinismith/embed/MWxdOqZ?default-tab=css%2Cresult" frameborder="no" loading="lazy" allowtransparency="" allowfullscreen=""></iframe>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2021/03/04/moonrise-demo-poster@512x512.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2021/03/04/moonrise-demo-poster@512x512.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">hivernal</title>
      <link href="https://henry.catalinismith.se/2021/02/21/hivernal/" rel="alternate" type="text/html" title="hivernal" />
      <published>2021-02-21T00:00:00+01:00</published>
      <updated>2021-02-21T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2021/02/21/hivernal</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2021/02/21/hivernal/"><![CDATA[<video controls="" preload="none" poster="/2021/02/21/hivernal-demo-poster@512x512.webp">
 <source src="/2021/02/21/hivernal-demo@512x512.mp4" type="video/mp4" />
</video>

<p>Winter animation in pure CSS.</p>

<iframe height="300" style="width: 100%;" scrolling="no" title="Hivernal" src="https://codepen.io/henrycatalinismith/embed/OJqYOwj?default-tab=css%2Cresult" frameborder="no" loading="lazy" allowtransparency="" allowfullscreen=""></iframe>

<h2 id="how-this-works">How This Works</h2>

<h3 id="pure-css">Pure CSS</h3>

<p>This is a pure CSS implementation of a Pythagoras tree with a bit of snow as an
added flourish. By “pure CSS” I mean no extra HTML elements except for the bare
minimum required to deliver the CSS in a valid way, so no <code class="language-plaintext highlighter-rouge">&lt;div class="tree"&gt;</code>
helper elements or anything like that.</p>

<h3 id="pythagoras-tree">Pythagoras tree</h3>

<p>The tree is based on the
<a href="https://rosettacode.org/wiki/Pythagoras_tree">Pythagoras tree example code on Rosetta Code</a>.
I haven’t found anywhere else that someone’s built a pythagoras tree in CSS like
this, so here’s how mine works.</p>

<p>I set a brown background on the <code class="language-plaintext highlighter-rouge">::after</code> pseudo-element on the body, and then
use <code class="language-plaintext highlighter-rouge">clip-path</code> to hide the parts outside the tree. To create the wind effect on
the branches, I generate two slightly different trees and use a keyframe
animation to bounce back and forth between them.</p>

<h3 id="snow">Snow</h3>

<p>The snow uses the same combination of <code class="language-plaintext highlighter-rouge">background-color</code> and <code class="language-plaintext highlighter-rouge">clip-path</code> as the
tree, except with circles instead of polygons. The snow moves left as it falls
in time with the swaying of the tree, which is supposed to create a bit of a
wind effect.</p>

<h3 id="performance-trade-offs">Performance trade-offs</h3>

<p>This one has slightly better performance than the CPU melting
<a href="/2020/12/01/doomfire/">doomfire animation</a> I made a couple of months ago, but
it’s still not really a technique you’d want in a production website. Who cares
though? It’s fun as hell making these.</p>

<p>And just like with doomfire, one thing I really like about this is that it takes
advantage of one of the unique strengths of CSS. In this case, it’s the swaying
tree animation. Almost everywhere you find pythagoras trees, they’re a
completely static image. CSS made it fairly easy to bring this one to life with
the keyframe animation, whereas most other technologies would require you to
write a bunch of extra code for that.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2021/02/21/hivernal-demo-poster@512x512.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2021/02/21/hivernal-demo-poster@512x512.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">doomfire</title>
      <link href="https://henry.catalinismith.se/2020/12/01/doomfire/" rel="alternate" type="text/html" title="doomfire" />
      <published>2020-12-01T00:00:00+01:00</published>
      <updated>2020-12-01T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2020/12/01/doomfire</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2020/12/01/doomfire/"><![CDATA[<video controls="" preload="none" poster="/2020/12/01/doomfire-demo-poster@512x512.webp">
 <source src="/2020/12/01/doomfire-demo@512x512.mp4" type="video/mp4" />
</video>

<p>Fire animation in pure CSS inspired by the PlayStation version of DOOM.</p>

<iframe height="300" style="width: 100%" scrolling="no" title="doomfire" src="https://codepen.io/henrycatalinismith/embed/preview/jOrraKw?default-tab=css%2Cresult" frameborder="no" loading="lazy" allowtransparency="" allowfullscreen=""></iframe>

<h2 id="how-this-works">How This Works</h2>

<p>The algorithm for the fire itself is from Fabien Sanglard’s
<a href="https://fabiensanglard.net/doom_fire_psx/">How DOOM fire was done</a>. Fabien’s
write-up was so helpful that it inspired me to try and pay it forward by
explaining how my implementation works. So here goes!</p>

<h3 id="pure-css">Pure CSS</h3>

<p>It’s quite common for CSS animation demos like these to include a bunch of
placeholder HTML elements. Typically it’ll be like a bunch of <code class="language-plaintext highlighter-rouge">&lt;div&gt;</code> elements
for the CSS to animate.</p>

<p>This one doesn’t have any of that, which is what I mean when I call it a “pure
CSS” implementation. There’s no HTML involved except for the bare minimum
required to deliver the CSS in a valid way. What this means is that at its very
core, the whole animation piggybacks off a pseudo element on the <code class="language-plaintext highlighter-rouge">&lt;body&gt;</code> tag.</p>

<h3 id="one-square-one-frame">One square, one frame</h3>

<p>We need one static black square as a starting point.</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">body</span><span class="nd">::after</span> <span class="p">{</span>
  <span class="nl">background</span><span class="p">:</span> <span class="nx">black</span><span class="p">;</span>
  <span class="nl">content</span><span class="p">:</span> <span class="s1">""</span><span class="p">;</span>
  <span class="nl">position</span><span class="p">:</span> <span class="nb">absolute</span><span class="p">;</span>
  <span class="nl">width</span><span class="p">:</span> <span class="m">128px</span><span class="p">;</span>
  <span class="nl">height</span><span class="p">:</span> <span class="m">128px</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<figure>
 <img alt="White square with a black square in the top left." src="/2020/12/01/doomfire-example-output-1@256x256.webp" />
 <figcaption>
  The black square is the <code>::after</code> pseudo-element.
 </figcaption>
</figure>

<p>The black square is the <code class="language-plaintext highlighter-rouge">::after</code> pseudo-element.</p>

<h3 id="two-squares-one-frame">Two squares, one frame</h3>

<p>The next step is to turn that 1x1 black square into a whole 2D screen, and the
magic words for that is box-shadow. In most everyday web development, box
shadows are blurry and obscured by the element they belong to. To use them for
this kind of magic, there are three rules:</p>

<ol>
  <li>Hide the element itself: no background color.</li>
  <li>Position them so that they’re completely outside the element.</li>
  <li>Don’t set a blur radius.</li>
</ol>

<p>So here’s an updated example using the above technique to increase the
resolution from 1x1 to 1x2.</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">body</span><span class="nd">::after</span> <span class="p">{</span>
  <span class="nl">content</span><span class="p">:</span> <span class="s1">""</span><span class="p">;</span>
  <span class="nl">position</span><span class="p">:</span> <span class="nb">absolute</span><span class="p">;</span>
  <span class="nl">width</span><span class="p">:</span> <span class="m">128px</span><span class="p">;</span>
  <span class="nl">height</span><span class="p">:</span> <span class="m">128px</span><span class="p">;</span>
  <span class="nl">box-shadow</span><span class="p">:</span>
    <span class="m">128px</span> <span class="nf">calc</span><span class="p">(</span><span class="m">128px</span> <span class="o">*</span> <span class="m">0</span><span class="p">)</span> <span class="nx">yellow</span><span class="p">,</span>
    <span class="m">128px</span> <span class="nf">calc</span><span class="p">(</span><span class="m">128px</span> <span class="o">*</span> <span class="m">1</span><span class="p">)</span> <span class="nx">red</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<figure>
 <img alt="White square with a yellow square in the top right and a red one in the bottom right." src="/2020/12/01/doomfire-example-output-2@256x256.webp" />
 <figcaption>
  The yellow square is drawn by the first <code>box-shadow</code> and the red square is drawn by the second one.
 </figcaption>
</figure>

<p>The yellow square is drawn by the first <code class="language-plaintext highlighter-rouge">box-shadow</code> and the red square is drawn
by the second one.</p>

<h3 id="two-squares-three-frames">Two squares, three frames</h3>

<p>From here, increasing the resolution beyond 1x2 is a matter of adding more box
shadows to the list. And making them update to look like a fire is a keyframe
animation.</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">@keyframes</span> <span class="n">fire</span> <span class="p">{</span>
  <span class="m">33%</span> <span class="p">{</span>
    <span class="nl">box-shadow</span><span class="p">:</span>
      <span class="m">128px</span> <span class="nf">calc</span><span class="p">(</span><span class="m">128px</span> <span class="o">*</span> <span class="m">0</span><span class="p">)</span> <span class="nx">white</span><span class="p">,</span>
      <span class="m">128px</span> <span class="nf">calc</span><span class="p">(</span><span class="m">128px</span> <span class="o">*</span> <span class="m">1</span><span class="p">)</span> <span class="nx">white</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="m">66%</span> <span class="p">{</span>
    <span class="nl">box-shadow</span><span class="p">:</span>
      <span class="m">128px</span> <span class="nf">calc</span><span class="p">(</span><span class="m">128px</span> <span class="o">*</span> <span class="m">0</span><span class="p">)</span> <span class="nx">white</span><span class="p">,</span>
      <span class="m">128px</span> <span class="nf">calc</span><span class="p">(</span><span class="m">128px</span> <span class="o">*</span> <span class="m">1</span><span class="p">)</span> <span class="nx">red</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="m">99%</span> <span class="p">{</span>
    <span class="nl">box-shadow</span><span class="p">:</span>
      <span class="m">128px</span> <span class="nf">calc</span><span class="p">(</span><span class="m">128px</span> <span class="o">*</span> <span class="m">0</span><span class="p">)</span> <span class="nx">yellow</span><span class="p">,</span>
      <span class="m">128px</span> <span class="nf">calc</span><span class="p">(</span><span class="m">128px</span> <span class="o">*</span> <span class="m">1</span><span class="p">)</span> <span class="nx">red</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<figure>
 <img alt="White square with a black square in the top left." src="/2020/12/01/doomfire-example-output-1@256x256.webp" />
 <figcaption>
  The black square is the <code>::after</code> pseudo-element.
 </figcaption>
</figure>

<p>Now it’s starting to look like a fire.</p>

<h3 id="performance-trade-offs">Performance trade-offs</h3>

<p>One of the downsides of this technique is how inefficient it is. Box shadows
don’t animate efficiently even at small scale and this takes them to an extreme.
On the bright side, the heating effect this fire animation has on your CPU adds
an extra level of immersion and realism to the experience.</p>

<p>And because every pixel in every frame of the animation needs to be declared
explicitly, this is a bandwidth hog too. This 16 second animation is 3.5
megabytes of CSS! Rather than have it cut out abruptly after 16 seconds, I’ve
made it loop infinitely. And fortunately enough, the loop is really seamless.
But to make it loop, I’ve had to cut the part at the beginning where the fire
spreads in from the bottom, which is a shame.</p>

<p>It’s not all downsides though. The original DOOM implementation has a real
videogame look to it, and the reason this implementation looks a bit more like
real flames is thanks to CSS. While the example code above uses a square pseudo
element, the real animation uses a long thin one. This stretches the flames out
a bit vertically, without changing anything about the algorithm powering it.
Combined with the blur filter, I think it produces a pretty neat effect!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2020/12/01/doomfire-demo-poster@512x512.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2020/12/01/doomfire-demo-poster@512x512.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">itsy studio</title>
      <link href="https://henry.catalinismith.se/2020/09/15/itsy-studio/" rel="alternate" type="text/html" title="itsy studio" />
      <published>2020-09-15T00:00:00+02:00</published>
      <updated>2020-09-15T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2020/09/15/itsy-studio</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2020/09/15/itsy-studio/"><![CDATA[<video controls="" preload="none" poster="/2020/09/15/itsy-studio@750x1334.webp">
 <source src="/2020/09/15/itsy-studio@574x1024.mp4" type="video/mp4" />
</video>

<p>I tried to make a mobile version of the PICO-8 fantasy console. It’s unfinished
and abandoned, but this video lives on as proof of how far I got. Had to write
<a href="https://codeberg.org/henrycatalinismith/itsy/src/branch/main/itsyplay">a lot of C code</a>
for this, and dealt with a lot of difficult issues passing data back and forth
between React Native, web views, and web assembly.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      
        <category term="gamedev" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2020/09/15/itsy-studio@750x1334.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2020/09/15/itsy-studio@750x1334.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Natural Cycles Animation</title>
      <link href="https://henry.catalinismith.se/2020/04/22/natural-cycles-animation/" rel="alternate" type="text/html" title="Natural Cycles Animation" />
      <published>2020-04-22T00:00:00+02:00</published>
      <updated>2020-04-22T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2020/04/22/natural-cycles-animation</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2020/04/22/natural-cycles-animation/"><![CDATA[<video controls="" preload="none" poster="/2020/04/22/natural-cycles-animation@2048x1210.webp">
 <source src="/2020/04/22/natural-cycles-animation@512x302.mp4" type="video/mp4" />
</video>

<p>Explanatory animation about the Natural Cycles algorithm. I came up with this
concept and implemented it as an A/B test for the “How It Works” page on the
Natural Cycles website. It generated enough of an uplift in conversion rate that
we kept it after the test concluded, until it was eventually displaced by a
later successful A/B test result. The animation is powered by React and CSS.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2020/04/22/natural-cycles-animation@2048x1210.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2020/04/22/natural-cycles-animation@2048x1210.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Högdalen</title>
      <link href="https://henry.catalinismith.se/2018/05/04/hogdalen/" rel="alternate" type="text/html" title="Högdalen" />
      <published>2018-05-04T00:00:00+02:00</published>
      <updated>2018-05-04T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2018/05/04/hogdalen</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2018/05/04/hogdalen/"><![CDATA[<figure>
 <img src="/2018/05/04/hogdalen@3021x3021.webp" alt="Busy street scene with people coming and going, multicolored bunting hanging above, as well as a circular white sign with a red rooster symbol. The same symbol is emblazoned in a 3 meter tall sign on a tall building nearby, underneath a large sign saying “Högdalen”." />
</figure>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2018/05/04/hogdalen@3021x3021.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2018/05/04/hogdalen@3021x3021.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Home</title>
      <link href="https://henry.catalinismith.se/2017/07/17/home/" rel="alternate" type="text/html" title="Home" />
      <published>2017-07-17T00:00:00+02:00</published>
      <updated>2017-07-17T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2017/07/17/home</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2017/07/17/home/"><![CDATA[<figure>
 <img src="/2017/07/17/home@2259x2259.webp" alt="White bungalow with a grey roof on a clear sunny day. A white car and van are parked outside on a gravel drive." />
</figure>

<p>Miss this place.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2017/07/17/home@2259x2259.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2017/07/17/home@2259x2259.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">OK Google Say Hey Siri Tell Me My Name</title>
      <link href="https://henry.catalinismith.se/2017/05/18/ok-google-say-hey-siri-tell-me-my-name/" rel="alternate" type="text/html" title="OK Google Say Hey Siri Tell Me My Name" />
      <published>2017-05-18T00:00:00+02:00</published>
      <updated>2017-05-18T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2017/05/18/ok-google-say-hey-siri-tell-me-my-name</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2017/05/18/ok-google-say-hey-siri-tell-me-my-name/"><![CDATA[<video controls="" preload="none" poster="/2017/05/18/ok-google-say-hey-siri-tell-me-my-name@960x540.webp">
 <source src="/2017/05/18/ok-google-say-hey-siri-tell-me-my-name@512x288.mp4" type="video/mp4" />
</video>

<p>Some people say Discover Weekly is the most important innovation ever to come
out of a Hack Week at Spotify. Those people are wrong.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2017/05/18/ok-google-say-hey-siri-tell-me-my-name@960x540.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2017/05/18/ok-google-say-hey-siri-tell-me-my-name@960x540.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Spotify Boilerplate App</title>
      <link href="https://henry.catalinismith.se/2017/02/12/spotify-boilerplate-app/" rel="alternate" type="text/html" title="Spotify Boilerplate App" />
      <published>2017-02-12T00:00:00+01:00</published>
      <updated>2017-02-12T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2017/02/12/spotify-boilerplate-app</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2017/02/12/spotify-boilerplate-app/"><![CDATA[<video controls="" preload="none" poster="/2017/02/12/spotify-boilerplate-app@512x289.webp" loop="">
 <source src="/2017/02/12/spotify-boilerplate-app@512x290.mp4" type="video/mp4" />
</video>

<p>By 2017 there was consensus among the Spotify desktop team that we wanted new
features to be built in React, but somehow it wasn’t quite happening yet. I took
it upon myself to help address this by rebuilding the “boilerplate” app in
React. I put a lot of love into this to try to make it something that would get
people feeling excited and positive.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2017/02/12/spotify-boilerplate-app@512x289.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2017/02/12/spotify-boilerplate-app@512x289.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Guillotine</title>
      <link href="https://henry.catalinismith.se/2017/01/21/guillotine/" rel="alternate" type="text/html" title="Guillotine" />
      <published>2017-01-21T00:00:00+01:00</published>
      <updated>2017-01-21T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2017/01/21/guillotine</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2017/01/21/guillotine/"><![CDATA[<video controls="" preload="none" poster="/2017/01/21/guillotine@512x288.webp">
 <source src="/2017/01/21/guillotine@512x288.mp4" type="video/mp4" />
</video>

<p>A lot of people made
<a href="https://www.avclub.com/take-a-load-off-by-watching-remixes-of-richard-spencer-1798256950">one of these</a>,
and this is mine.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2017/01/21/guillotine@512x288.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2017/01/21/guillotine@512x288.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Rap do Bonde</title>
      <link href="https://henry.catalinismith.se/2016/12/15/rap-do-bonde/" rel="alternate" type="text/html" title="Rap do Bonde" />
      <published>2016-12-15T00:00:00+01:00</published>
      <updated>2016-12-15T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2016/12/15/rap-do-bonde</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2016/12/15/rap-do-bonde/"><![CDATA[<video controls="" preload="none" poster="/2016/12/15/rap-do-bonde@512x288.webp">
 <source src="/2016/12/15/rap-do-bonde@512x288.mp4" type="video/mp4" />
</video>

<p>Maybe you have to speak Swedish and Portuguese to appreciate this. Don’t care.
Can’t watch the intro to <em lang="sv"><a href="https://www.tv4.se/bonde">Bonde
Söker Fru</a></em> without thinking about this song, and had to make this video
to get it out of my system. You have to watch to the end BTW.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="video" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2016/12/15/rap-do-bonde@512x288.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2016/12/15/rap-do-bonde@512x288.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Manhattan Trump Protest</title>
      <link href="https://henry.catalinismith.se/2016/11/09/manhattan-trump-protest/" rel="alternate" type="text/html" title="Manhattan Trump Protest" />
      <published>2016-11-09T00:00:00+01:00</published>
      <updated>2016-11-09T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2016/11/09/manhattan-trump-protest</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2016/11/09/manhattan-trump-protest/"><![CDATA[<figure>
 <img src="/2016/11/09/manhattan-trump-protest@1143x1143.webp" alt="City scene at night with a large crowd of people marching through a street in the rain. In the foreground a sign is visible that reads 'Fuck Donald Trump'." />
</figure>

<p>I was in New York for work during the 2016 presidential elections. The day
after, I got a warning email from some kind of employee travel safety company
saying to stay away from Union Square that night because there was going to be a
big protest. That’s me in the bottom left of the picture next to the woman with
the camera.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="organising" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2016/11/09/manhattan-trump-protest@1143x1143.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2016/11/09/manhattan-trump-protest@1143x1143.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">No Cigar</title>
      <link href="https://henry.catalinismith.se/2016/04/26/no-cigar/" rel="alternate" type="text/html" title="No Cigar" />
      <published>2016-04-26T00:00:00+02:00</published>
      <updated>2016-04-26T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2016/04/26/no-cigar</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2016/04/26/no-cigar/"><![CDATA[<video controls="" preload="none" poster="/2016/04/26/no-cigar@302x522.webp">
 <source src="/2016/04/26/no-cigar@302x522.mp4" type="video/mp4" />
</video>

<p>Skating in Stockholm in April. Hate it here.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2016/04/26/no-cigar@302x522.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2016/04/26/no-cigar@302x522.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Lost in space</title>
      <link href="https://henry.catalinismith.se/2015/11/06/lost-in-space/" rel="alternate" type="text/html" title="Lost in space" />
      <published>2015-11-06T00:00:00+01:00</published>
      <updated>2015-11-06T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2015/11/06/lost-in-space</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2015/11/06/lost-in-space/"><![CDATA[<p>Simulating gravity has made all the other space-related code I’ve written so far
for this project seem easy. This has been one of those demoralising problems
that you think you’ve solved until you try a new test scenario and suddenly
you’re back at square one because it turns out it was never really working right
at all. For a good while I genuinely didn’t think I was going to be able to make
it work at all.</p>

<h2 id="the-equation">The equation</h2>

<p>On the face of it, gravity’s really simple. It’s a tiny little formula that
converts a body’s mass and your distance from it into your gravitational
acceleration.</p>

<figure>
 <img src="/2015/11/06/lost-in-space-equation@640x360.webp" alt="Newton's law of universal gravitation: g = GM/r2" />
 <figcaption>
  Easy.
 </figcaption>
</figure>

<h2 id="first-attempts">First attempts</h2>

<p>At least, it would have been that simple if I’d done a better job of the initial
research. My first attempts were using a totally different and wrong equation
that I hacked together based on an incomplete understanding of various things
I’d hastily skim-read from Google.</p>

<p>I threw a bunch of code together, all excited to see some proper space stuff
happening. The first test run felt so climactic that I recorded it. Here it is.
Even if you really sucked at physics in school, I promise you’re gonna be able
to spot the problem. Check it out.</p>

<figure>
 <video poster="/2015/11/06/lost-in-space-first-attempt-poster@564x472.webp" src="/2015/11/06/lost-in-space-first-attempt@564x472.mp4" title="The Earth and Sun moving away from each other instead of towards each other." controls="" preload="none" playsinline="">
 </video>
 <figcaption>
  You had one job.
 </figcaption>
</figure>

<p>Yep, it’s backwards. The Earth takes one look at the Sun, turns around, and
heads off into deep space. Even worse, the Sun itself seems to be being affected
way more than it should by Earth’s comparatively tiny gravitational force.</p>

<h2 id="if-at-first-you-dont-succeed">If at first you don’t succeed</h2>

<p>There were lots more failed attempts shortly after this. Each one was more
confusing than the last. I didn’t know what I was doing. I kept making little
tweaks to the calculations without really understanding why. No wonder none of
them worked.</p>

<p>This went on for a few days. Gradually I started to slow down and get a bit more
methodical. Lots of stepping through equations to try to get a feel for the
numbers. And yes, I use the word “Sol” instead of just “Sun” in one of the
diagrams, purely because it felt more sciencey to write 😎</p>

<figure>
 <img src="/2015/11/06/lost-in-space-board1@700x525.webp" alt="Whiteboard diagram with Newton's gravity formula and some planets with lots of little numbers and arrows." />
 <figcaption>
  Maths is hard.
 </figcaption>
</figure>

<p>After a few more days, the gravity calculation was sort of working. It was still
miles away from being anything close to correct, but good enough progress to
keep me interested.</p>

<h2 id="bodge-it-and-scarper">Bodge it and scarper</h2>

<p>I thought I was close to nailing it. When I saw it get the Moon to orbit the
Earth, I was so happy I took another screen recording. This was around about the
same time I discovered that deleting the call to clearRect made it leave behind
a cool orbit trail.</p>

<figure>
 <video poster="/2015/11/06/lost-in-space-one-moon-poster@474x414.webp" src="/2015/11/06/lost-in-space-one-moon@474x414.mp4" title="The Moon moving around the Earth, leaving a little white trail as it goes." controls="" preload="none" playsinline="">
 </video>
 <figcaption>
  The bit at the very end where it completes the orbit and returns to its exact point of origin is my favorite.
 </figcaption>
</figure>

<p>Wow, I was so pleased with that. Thing is, I’d had to grossly inflate the mass
of the Earth to make it work like that, and I had no idea why. Everything was
sort of working, but full of these weird little hacky tweaks that I didn’t know
why they helped.</p>

<p>By now I was using the right gravity equation. But I had to subtract the result
from 0 to make it work. Again, no idea why. It was just something I did at one
point to see if it helped, and it did, so I left it in.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const acceleration = 0 - (
  (G * subject.mass) /
  (Math.pow(distance, 2))
)
</code></pre></div></div>

<h2 id="me-reaping-well-this-fucking-sucks-what-the-fuck">Me reaping: Well this fucking sucks. What the fuck.</h2>

<p>So I definitely had a rude awakening coming. It came when I decided to test it
with four Moons orbiting the Earth. They were positioned equidistant around the
Earth and initialised with what should have been anti-clockwise orbits of equal
sizes. What should happen in this next video is basically the same as in the
last one except with four circles instead of one.</p>

<figure>
 <video poster="/2015/11/06/lost-in-space-terrible-poster@950x656.webp" src="/2015/11/06/lost-in-space-terrible@950x656.mp4" title="Four moons orbiting the Earth, except it all goes immediately wrong and they fly away." controls="" preload="none" playsinline="">
 </video>
 <figcaption>
  Not a single thing that happens in this video is right.
 </figcaption>
</figure>

<p>Backwards gravity was back, and yet again, I had no idea why. It was
demoralising as fuck.</p>

<p>The only thing to do was keep on going and try to get a little bit more serious
and careful with each step. The thing about building this is that the gravity
calculation is only a tiny part of it. Most of the work so far has gone into the
geometry code. You need to be able to calculate angles and distances between
points, add vectors together, and calculate vectors from an angle and a
magnitude. Lots of Math.atan2 and that sort of thing.</p>

<p>The gravity calculation was okay, so I knew the problem was elsewhere and
started digging around accordingly. There was something wrong with how I was
applying the gravity to the planets. I started to try to tighten up my
understanding of the basic geometry of this imaginary universe I’d built.</p>

<p><img src="/2015/11/06/lost-in-space-board3@700x456.webp" alt="
Whiteboard diagram of a 2D grid with two points on it and an angle drawn between them.
Next to that, a pair of arrows compare 'current direction' with 'actual direction'." /></p>

<figure>
 <img src="/2015/11/06/lost-in-space-board3@700x456.webp" alt="
   Whiteboard diagram of a 2D grid with two points on it and an angle drawn between them.
   Next to that, a pair of arrows compare 'current direction' with 'actual direction'." />
 <figcaption>
  I literally didn't understand radians until this point.
 </figcaption>
</figure>

<p>Once I understood the basics a little better, I did what I always do when I’m as
lost as this: I wrote tests. So many fucking tests… Just, basic things like
putting two points next to each other and checking that the distances and angles
are being calculated right. I wrote dozens of tests like that.</p>

<p>While I was writing a crapload of tests, I made a few teeny tiny little tweaks
along the way. None of it seemed major. Somehow, though, things started working
a bit better. I set the “four moons” test case back up, ready to be disappointed
and feel stupid.</p>

<figure>
 <video poster="/2015/11/06/lost-in-space-four-moons-poster@744x604.webp" src="/2015/11/06/lost-in-space-four-moons@744x604.mp4" title="Four moons orbiting the Earth beautifully and producing a lovely geometric pattern." controls="" preload="none" playsinline="">
 </video>
 <figcaption>
  Holy shit.
 </figcaption>
</figure>

<p>Almost perfect! This is where I’m at right now, and it’s good enough to consider
moving on to whatever the next problem will be. It’s still a long way from
perfect, but I haven’t seen any “backwards gravity” and to be honest, that’ll
do.</p>

<p>Earth’s orbit around the Sun takes about 10 days longer than it should. And the
Moon’s orbit around the Earth takes about half the time it should. Just like
before, I haven’t got the slightest idea why. But this project doesn’t really
have any explicit goals beyond “build something that makes my computer do some
cool space stuff, and have fun doing it”. And it’s doing the cool space stuff
well enough to be fun now!</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[Simulating gravity has made all the other space-related code I’ve written so far for this project seem easy. This has been one of those demoralising problems that you think you’ve solved until you try a new test scenario and suddenly you’re back at square one because it turns out it was never really working right at all. For a good while I genuinely didn’t think I was going to be able to make it work at all.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2015/11/06/lost-in-space-first-attempt-poster@564x472.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2015/11/06/lost-in-space-first-attempt-poster@564x472.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Scalable spacetime</title>
      <link href="https://henry.catalinismith.se/2015/11/02/scalable-spacetime/" rel="alternate" type="text/html" title="Scalable spacetime" />
      <published>2015-11-02T00:00:00+01:00</published>
      <updated>2015-11-02T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2015/11/02/scalable-spacetime</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2015/11/02/scalable-spacetime/"><![CDATA[<p>After building last week’s Solar System renderer, I was excited to move on to
the fun stuff and see some planets moving around. I rushed on into implementing
motion, which on the face of it is pretty simple. If Earth is moving along at
30,000m/s, for example, then you can just add its velocity to its current
position once per second and get its new location.</p>

<figure>
 <img alt="
   Two blue circles separated horizontally by an arrow pointing right labelled 30,000m.
   The circle on the left is labelled ‘0 seconds’ and the one on the right is labelled ‘1 second later'." src="/2015/11/02/scalable-spacetime-simple@640x360.webp" />
 <figcaption>
  We're moving, Rosie!
 </figcaption>
</figure>

<p>The code for this is an easy read. I threw together a little Motion class. So if
Earth has a vector of 30000 horizontally and 0 vertically, then each tick of
this method counts as one second of motion in my imaginary 2D universe, and it
moves the earth along by 30km each time.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Motion {
  affect (universe) {
  universe.planets.map((planet) =&gt; {
    planet.position.add(planet.vector);
  });
  }
}
</code></pre></div></div>

<p>This worked on my first try, which as usual, meant I’d barely broken the surface
of the problem. To give you a sense of perspective, here’s ten seconds of motion
in plain old 1:1 time. It’s not exactly riveting. Earth’s 12,000km diameter
makes 30km/s look slow. Honestly, I kind of thought it’d be a bit quicker, even
at 1:1 scale. Remember this next time somebody’s talking about how we’re
“hurtling through space on a giant rock”. It barely moves at all in this clip.</p>

<figure>
 <video poster="/2015/11/02/scalable-spacetime-slow-poster@540x346.webp" src="/2015/11/02/scalable-spacetime-slow@540x346.mp4" title="2D earth moving almost imperceptibly slowly." controls="" preload="none" playsinline="">
 </video>
 <figcaption>
  Hurtling my arse.
 </figcaption>
</figure>

<p>No way I’m waiting an entire real-life year to see my simulated Earth make its
way around the Sun once. What I needed was a way to speed up time.</p>

<p>Something I quickly realised was that this was more or less the same problem as
the viewport scaling, just dressed up a little differently. The symmetry carries
over into the solution, too, because just like in the graphical layer, I ended
up creating a scale property to represent the conversion rate. This time, it was
for converting between real life seconds and imaginary space seconds. So a scale
of 1 gives you one second per real second, and a scale of 60 gives you a minute
per real second, and so on.</p>

<p>The timing code is too involved (💤) to show a decent summarised version here.
The gist of it is that for each tick, it multiplies the real life tick interval
length by the scale factor, and passes that to any physics calculations so that
they can multiply their results by that. For motion, that just means multiplying
the planet’s vector by the number of seconds elapsed before adding it to the
planet’s position.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Motion {
  affect (universe, seconds) {
  universe.planets.map((planet) =&gt; {
    planet.position.add(
    planet.vector.scale(seconds)
    );
  });
  }
}
</code></pre></div></div>

<p>Now here’s the Earth again, this time with scale pumped up to 600. That’s 10
imaginary space minutes for every real world second. If you’re good at maths you
might notice that this video is 18 seconds long, and the timer doesn’t make it
to 3 hours like it should at this scale. But then if you were paying that kind
of attention you’d have noticed that only 10 seconds pass in the 12 second video
earlier! Still got some minor issues to iron out there 😇</p>

<figure>
 <video poster="/2015/11/02/scalable-spacetime-fast-poster@538x342.webp" src="/2015/11/02/scalable-spacetime-fast@538x342.mp4" title="2D earth moving much faster." controls="" preload="none" playsinline="">
 </video>
 <figcaption>
  Now <strong>this</strong> is podracing.
 </figcaption>
</figure>

<p>This is going great so far. It isn’t perfect, but I’m reasonably pleased with
it. Getting the foundational stuff like graphics and timing right makes it so
much more enjoyable to build the complicated stuff on top.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[After building last week’s Solar System renderer, I was excited to move on to the fun stuff and see some planets moving around. I rushed on into implementing motion, which on the face of it is pretty simple. If Earth is moving along at 30,000m/s, for example, then you can just add its velocity to its current position once per second and get its new location.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2015/11/02/scalable-spacetime-slow-poster@540x346.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2015/11/02/scalable-spacetime-slow-poster@540x346.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Apple Pie From Scratch</title>
      <link href="https://henry.catalinismith.se/2015/10/29/apple-pie-from-scratch/" rel="alternate" type="text/html" title="Apple Pie From Scratch" />
      <published>2015-10-29T00:00:00+01:00</published>
      <updated>2015-10-29T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2015/10/29/apple-pie-from-scratch</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2015/10/29/apple-pie-from-scratch/"><![CDATA[<p>This is a quick write-up of my solution to one of the first problems I bumped
into in my latest project. I’m building a JavaScript model of the solar system,
which in the end will hopefully feature all the planets orbiting the Sun
according to some approximation of Newtonian gravity.</p>

<p>The problem arose because it’s easier to implement the equations if your data
model uses SI units: meters, kilograms and seconds. Plus, if you adopt a nice
convention like SI units, it makes the code so much more readable. I find names
like distanceInKm or massInKg super ugly. It’s nicer if you can use distance and
mass and just infer the units from the convention.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Planet {
  constructor ({ mass, position, radius }) {
  this.mass = mass;
  this.position = position; // { x, y }
  this.radius = radius;
  }
}
</code></pre></div></div>

<p>This is lovely, but the CanvasRenderingContext2D API works in pixels. Take it or
leave it. So the code in charge of rendering planets needs to know how to
convert between meters and pixels.</p>

<p>It’s the kind of problem that’s tempting to sort of hack your way through. Just
keep throwing code at the wall and eventually something will stick. Easy enough,
but the result is usually not great. It’s bad enough not understanding your own
code a few weeks after writing it. Not understanding it while you’re still
writing it is just… no fun.</p>

<p>So I made a cup of tea, cleared a whiteboard, and had a bit of a think. The
resulting diagram is a bit incomprehensible, but I sometimes think it’d be nice
if people were more open about how lost they get with even simple stuff like
this, so here it is.</p>

<p>The gist of the solution is to have a scale property somewhere, denoting the
conversion rate between pixels and meters. Next, I needed there to be a “thing”
in my system to actually own that property and apply it. The name I settled on
for that “thing” was Viewport.</p>

<p>The Viewport needs to a few things to do its job. It needs that scale value, for
starters. It needs a reference to the rendering context, too. And I figured it
also needs to know which part of the universe it’s centered on, which I pass in
as an object called center with an x and a y property. Because x and y are
spatial values, you can just assume they’re in meters. See how nice that is?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Viewport {
  constructor({ center, context, scale }) {
  this.center = center; // { x, y }
  this.context = context;
  this.scale = scale;
  }
}
</code></pre></div></div>

<p>The last thing that’s missing is a Viewport method to render planets. Rendering
circles is nice and simple because it’s part of the browser API, so that’s a
freebie. What’s less simple is that I’ve designed the Viewport to accept the
coordinates of its center point in meters, whereas the built-in arc method
expects the coordinates of the circle in pixels from the top-left corner.</p>

<p>This is important, because I need it to be easy to center the viewport on a
given planet. I want it to be as simple as viewport.center = planet.position. As
usual, the price of simplicity somewhere is complexity elsewhere.</p>

<p>Converting between those two points is a two-step calculation. First, you have
to calculate the position of the top-left pixel of the canvas in imaginary space
meters. Then you subtract the coordinates of that position from the coordinates
of the planet, and multiply the results by the scale property from earlier. This
gives you the pixel position of the planet within the canvas. The resulting code
is really heavy on arithmetic and took a while to get right.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>drawPlanet (planet) {
  const { width, height } = this.context.canvas;
  const topLeft = new Point(
  this.center.x - ((width / 2) * (1 / this.scale)),
  this.center.y - ((height / 2) * (1 / this.scale))
  );

  this.context.fillStyle = planet.color;
  this.context.beginPath();

  this.context.arc(
  (planet.position.x - topLeft.x) * this.scale,
  (planet.position.y - topLeft.y) * this.scale,
  planet.radius * this.scale,
  0,
  Math.PI * 2
  );

  this.context.closePath();
  this.context.fill();
}
</code></pre></div></div>

<p>With that in place, I now have a working graphical layer that understands how to
render my SI units data model onto a 2D canvas element.</p>

<p>It’s a really fundamental part of the system, so you don’t end up with much to
show for all the effort, but I did manage to squeeze one cool thing out of it.
By gradually shrinking the value of the scale property, I can generate a “flying
backwards through space” effect. Check it out. Right at the very end you can see
the Moon whizz by on the right once we’re far enough from Earth.</p>

<figure>
 <video src="/2015/10/29/apple-pie-from-scratch-zoom@960x540.mp4" poster="/2015/10/29/apple-pie-from-scratch-zoom@960x540.webp" title="2D earth fading into the distance as the camera moves backwards" controls="" preload="none" playsinline="">
 </video>
 <figcaption>
  Fun, fun, fun.
 </figcaption>
</figure>

<p>Now that the basics are in place to display what’s going on, the next step is to
get everything moving. Right now I’m still struggling to make gravity work
right. It’s a lot harder than this was.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[This is a quick write-up of my solution to one of the first problems I bumped into in my latest project. I’m building a JavaScript model of the solar system, which in the end will hopefully feature all the planets orbiting the Sun according to some approximation of Newtonian gravity.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2015/10/29/apple-pie-from-scratch-zoom@960x540.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2015/10/29/apple-pie-from-scratch-zoom@960x540.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">DownloadHelper Source</title>
      <link href="https://henry.catalinismith.se/2015/07/29/downloadhelper-source/" rel="alternate" type="text/html" title="DownloadHelper Source" />
      <published>2015-07-29T00:00:00+02:00</published>
      <updated>2015-07-29T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2015/07/29/downloadhelper-source</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2015/07/29/downloadhelper-source/"><![CDATA[<figure>
 <img src="/2015/07/29/downloadhelper-source@512x512.webp" alt="Two printed A4 sheets of paper stapled together with source code on them and red pen markings." />
</figure>

<p>Did some music streaming DRM work once upon a time. The goal I was given was to
get the music to play in the browser using the Web Audio API without the
<a href="https://www.downloadhelper.net/">DownloadHelper</a> extension being able to
download the track to the user’s computer as an <code class="language-plaintext highlighter-rouge">.mp3</code> file. The extension’s
source code was quite obfucated – I think maybe deliberately, to fuck with
people like me trying to break it for The Man – so I ended up having to print it
out and debug it the old fashioned way with a red pen.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="music" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2015/07/29/downloadhelper-source@512x512.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2015/07/29/downloadhelper-source@512x512.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Gaming’s Persecution Complex</title>
      <link href="https://henry.catalinismith.se/2014/12/26/gamings-persecution-complex/" rel="alternate" type="text/html" title="Gaming’s Persecution Complex" />
      <published>2014-12-26T00:00:00+01:00</published>
      <updated>2014-12-26T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2014/12/26/gamings-persecution-complex</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2014/12/26/gamings-persecution-complex/"><![CDATA[<p>I watched a shit documentary recently. It was called
<em><a href="https://www.imdb.com/title/tt3214002/">Video Games: The Movie</a></em>. The central
theme was a kind of chronological look back through the history of the gaming
industry, which turned out to be a goldmine of little video clips of games from
decades ago. That part was alright. As a plain nostalgia session, it was okay.
Alongside that, though, was this weird little parallel story about “gamers”.</p>

<figure>
 <img alt="The promotional image for Video Games: The Movie, featuring many famous characters from Pac Man to Master Chief." src="/2014/12/26/gamings-persecution-complex-movie@700x394.webp" />
 <figcaption>
  pew pew pew
 </figcaption>
</figure>

<p>In the <em>Video Games: The Movie universe</em>, gamers are an oppressed social class.
It has all these talking heads-style interviews with various geek celebrities,
who tell the tale of all the things gamers have “survived”, such as “Congress”,
“busybody parents”, and “religious-based hysteria”. It got to the point where it
began to ruin the whole experience. Every time a segment on the history of games
was punctuated by one of these “free the gamers” bits, it totally broke my
engagement with the documentary. We were just laughing at it by the end.</p>

<p>This “oppressed gamer” identity is insidious. Maybe it’s a natural group
reaction to somebody like Jack Thompson. But it’s problematic because evidently
some people think it’s a license to play “culture war” and appropriate the words
and actions of genuinely oppressed social groups. There are even people trying
to make words like “anti-gamer” and “ludophobia” a thing. It’s bizarre, and they
seem oblivious the contradiction that access to this identity is predicated on
having enough economic privilege to buy expensive consumer electronics products.
It’s a way for those of us who live high atop Privilege Mountain to make believe
that we are in fact the plucky underdogs of society. We aren’t, and it’s
disgusting to pretend otherwise.</p>

<figure>
 <blockquote class="twitter-tweet">
  <p lang="en" dir="ltr">
   <a href="https://twitter.com/LemonSqueezy14?ref_src=twsrc%5Etfw">
    @LemonSqueezy14
   </a>
   <a href="https://twitter.com/VGAdjudicator?ref_src=twsrc%5Etfw">
    @VGAdjudicator
   </a>
   @hnrysmth Geeks have long been bullied &amp; ostracized.
   Playing Oppression Olympics will not change that. Ever
  </p>
  &mdash; AJ (@Wavinator)
  <a href="https://twitter.com/Wavinator/status/548545106769235968?ref_src=twsrc%5Etfw">
   December 26, 2014
  </a>
 </blockquote>
 <figcaption>
  Off to do a speech at the UN Forum on Minority Issues about the unique challenges of being really really good at Mario.
 </figcaption>
</figure>

<p>It’d be harmless if not for the group psychology that it triggers. The power of
this defensive impulse has been on display throughout the latter half of 2014.
There are a bunch of people right now who are really upset about Anita
Sarkeesian and her criticism of sexist tropes in video games. There’s another
bunch who are equally upset about Zoe Quinn and her free text adventure game
Depression Quest. In August, these two groups joined forces under the banner of
<a href="https://en.wikipedia.org/wiki/Gamergate_controversy">Gamergate</a>, and they’ve
spent the remainder of the year spewing bigotry and harassment like a volcano.</p>

<p>There are a lot of different groups of people involved. Right in the middle of
it all are the 8chan-style “Sociopath Libertarian” types who think even indecent
images of children should be protected free speech. Around them are the men’s
rights activists, the white supremacists, Tea Party supporters, and all kinds of
other right-wing dickheads. The biggest bunch of all though is this other group
of “general idiots” who are just sort of along for the ride. These are a largely
apolitical bunch who simply heard the “geek persecution” alarm bells and came
running for a fight. They mostly didn’t have a strong stance on issues like
feminism before August 2014, and the only reason they do now is because all the
men’s rights types have told them feminists are “anti-gamer”. If you’re
pro-Gamergate, you’re probably one of these idiots.</p>

<figure>
 <img alt="Hyper-sexualised video game advert featuring a woman in revealing clothes stood in the middle of a dusty marketplace." src="/2014/12/26/gamings-persecution-complex-ad@628x387.webp" />
 <figcaption>
  Those crazy feminist bitches didn't even like this Call Of
  Duty advert.
 </figcaption>
</figure>

<p>Years of pandering, epitomised by <em>Video Games: The Movie</em>, sowed the seeds of
illusory oppression in the minds of the gaming industry’s favourite demographic:
affluent young white guys. The Zach Braffs and Wil Wheatons of the world have
been patting gamers on the head and commiserating with them about what a
<em>struggle</em> it is being a white man with an expensive toy. The ongoing temper
tantrum about Leigh Alexander’s late August
<em><a href="https://www.gamasutra.com/view/news/224400/Gamers_dont_have_to_be_your_audience_Gamers_are_over.php">Gamers Are Over</a></em>
piece shows how entitled to that pandering some people now feel.</p>

<figure>
 <blockquote class="twitter-tweet">
  <p lang="en" dir="ltr">
   @hnrysmth Her articles I had always found to be hit or miss, but that one was vicious and just plain mean spirited
  </p>
  &mdash; Kaz Hebdo (@KazuukHadoken)
  <a href="https://twitter.com/KazuukHadoken/status/548525941686018048?ref_src=twsrc%5Etfw">
   December 26, 2014
  </a>
 </blockquote>
 <figcaption>
  They targeted gamers. Gamers.
 </figcaption>
</figure>

<p>Gamergate is gaming’s persecution complex in its final form: stupid, incoherent,
and angry. These people are standing firm alongside
<a href="https://gamergate.wikia.com/wiki/Davis_M.J._Aurini">white supremacists</a>,
<a href="https://ellorgast.tumblr.com/post/99422454733/gamergate-throwing-its-own-trans-supporters-under">transphobes</a>,
<a href="https://twitter.com/HW_BEAT_THAT/status/547013158116663296">distributors of CSAM</a>,
and even Jack Thompson himself. There is a genuine sense within Gamergate that
“gamer rights” are important enough to justify allegiances with bigots of all
kinds as long as it serves the cause. The only reason so many idiots are giving
these regressive monsters the time of day is because of this ongoing conspiracy
theory about geek persecution, perpetuated by garbage like <em>Video Games: The
Movie</em>.</p>

<p>Earlier this year I accidentally scared some people who saw the designs for
<a href="/2014/08/04/not-planning-any-nuclear-attacks">a game I was working on</a>. It got
a bit of
<a href="https://www.kotaku.com.au/2014/08/police-called-on-game-creator-over-nuclear-war-diagrams/">media attention</a>.
A few people accused me of making the whole thing up to sell copies of my (free)
game, but for the most part people gave me the benefit of the doubt. What I’ve
learned from Gamergate in the months since then is that, given a woman in
equivalent circumstances, there’s a part of the gaming community that will form
a mob to harass her and speculate about who she fucked at Kotaku to get that
press hit. And because of the gamer persecution complex, they’ll be able to
count on the wholehearted support of lots of people who ought to know better,
just by sounding the “gamer oppression” alarm while they do it.</p>

<p>So it turns out that pretending geeks are oppressed isn’t harmless fun after
all. It leads to very tangible, negative, immediate consequences in the real
world. Real people’s lives are affected. So it’s something we need to work on.
Be on the lookout for it, and call it out when you see it. Another example is
the phrase
”<a href="https://observationdeck.io9.com/got-to-stop-saying-nerd-blackface-1498747759">nerd blackface</a>”.
Cut that shit out. It’s not necessarily
<a href="https://kotaku.com/we-might-be-witnessing-the-death-of-an-identity-1628203079">the death of an identity</a>.
It’s more like the recategorisation of Pluto: “gamer” isn’t a marginalised
social group, so let’s stop pretending it is.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[I watched a shit documentary recently. It was called Video Games: The Movie. The central theme was a kind of chronological look back through the history of the gaming industry, which turned out to be a goldmine of little video clips of games from decades ago. That part was alright. As a plain nostalgia session, it was okay. Alongside that, though, was this weird little parallel story about “gamers”.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2014/12/26/gamings-persecution-complex-movie@700x394.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2014/12/26/gamings-persecution-complex-movie@700x394.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">That Weird Keyboard</title>
      <link href="https://henry.catalinismith.se/2014/11/16/that-weird-keyboard/" rel="alternate" type="text/html" title="That Weird Keyboard" />
      <published>2014-11-16T00:00:00+01:00</published>
      <updated>2014-11-16T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2014/11/16/that-weird-keyboard</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2014/11/16/that-weird-keyboard/"><![CDATA[<p>At the start of 2014 I started to pay the price for years and years of typing. A
horrible RSI was causing shooting pains in my wrists by mid-afternoon each day.
Something had to change, but obviously not typing any more wasn’t an option. The
solution I settled on was an ergonomic keyboard, specifically the
<a href="https://www.ergodox.io/">Ergodox</a>.</p>

<p>The Ergodox is an open source hardware design. Like almost anything involving
the words “open” and “source”, some assembly is required. The equipment you need
is fairly standard, and the
<a href="https://www.instructables.com/ErgoDox-Mechanical-Keyboard/">assembly instructions</a>
list it all. The only extra I’d recommend is a magnifying glass, to help inspect
those tiny diode solder joints and so on. Here’s what it all looked like just
before I got started. The
<a href="https://www.youtube.com/watch?v=x1irVrAl3Ts">video playing on the laptop</a> is a
35 minute close-up of somebody who knows what they’re doing completing the whole
build. It really helped.</p>

<figure>
 <img alt="
   Photo of my desk during the assembly process.
   A Macbook with a fullscreen video showing a closeup of the circuit board is on top of a large red Ferrari flag alongside many bags of parts.
   On the desk itself are a soldering iron, several tools, a plate full of components, and the keyboard's circuit boards." src="/2014/11/16/that-weird-keyboard-equipment@700x525.webp" />
 <figcaption>
  Everyone knows you do better soldering if there's a Brazilian flag nearby.
 </figcaption>
</figure>

<p>The big thing that people worry about is whether they’ll be able to cope with
soldering the surface mount diodes. It really isn’t that hard, and once you get
into a rhythm it’s kind of fun. The part I think is actually the most difficult
is stripping all the plastic off the USB connector. Unlike the diodes, you don’t
build up any practice with that step, so it’s a one shot deal. I actually fucked
it up the first time and had to get another one and do it again.</p>

<p>Once the whole thing was put together, the left hand keyboard didn’t work at
all. This was pretty bad news. I don’t know anywhere near enough about
electronics to debug a circuit board. It started to look like I’d spent all this
time and money on a failed project. I was out of ideas, so I spent about a day
miserably retouching solder joints and checking things with the multimeter.
Against all the odds, my desperate, clueless poking around randomly fixed
whatever was wrong. Easily one of the most undeserved moments of success in my
life.</p>

<figure>
 <img alt="Close-up photo of my keyboard's circuit boards with the surface mount diodes all soldered." src="/2014/11/16/that-weird-keyboard-board@700x400.webp" />
 <figcaption>
  This picture is quite lo-res and you can't really see these joints much better than this in real life without a magnifying glass.
 </figcaption>
</figure>

<p>The sheer difficulty of learning a new keyboard with no key labels was a major
surprise. It took a week of practice before I could type a whole sentence slowly
without a mistake. It took months for me to regain enough speed to use it in my
day job. Months. After almost a year, I can type as naturally and fast on my
Ergodox as I can on my laptop keyboard. Switching between the two is no problem
at all either. But it took about six months of daily use before I could type
without thinking about it.</p>

<p>Massdrop provide an excellent web-based
<a href="https://www.massdrop.com/ext/ergodox">keyboard layout configurator</a>.
Customising the layout was an experience full of lessons about ergonomics. Many
of the Ergodox’s buttons are great as modifiers or special keys, but terrible
for typing regular characters. The default layout pretends that this isn’t the
case, and mixes both types of key haphazardly across all the different
locations. Eventually I settled on
<a href="https://www.massdrop.com/ext/ergodox/?referer=G9MWLU&amp;hash=ee3b6bf44643182a0ddc2ec08222be69">the layout below</a>.</p>

<figure>
 <img alt="A fairly standard-looking QWERTY layout with all the keys more or less where they'd be on a normal keyboard." src="/2014/11/16/that-weird-keyboard-layout@700x277.webp" />
 <figcaption>
  Adding a modifier-free colon key and putting it right on the thumb cluster was a masterstroke.
 </figcaption>
</figure>

<p>The main principle behind my layout is that it mimics a normal QWERTY layout as
much as possible. Learning to type all over again is hard enough without
throwing in a new layout as well. Punctuation keys are as close to their usual
homes as possible too. Just a few of them are squeezed out onto the thumb key
clusters.</p>

<p>The left thumb keys deal with <code class="language-plaintext highlighter-rouge">[LCtrl]</code> and <code class="language-plaintext highlighter-rouge">[Esc]</code>, with <code class="language-plaintext highlighter-rouge">[Enter]</code> and
<code class="language-plaintext highlighter-rouge">[Backspace]</code> on the right thumb. These are some of the most frequently used
buttons on the keyboard, and it’s so much more physically comfortable having the
strong, dexterous thumbs deal with them instead of those poor overworked little
fingers.</p>

<p>Apart from the number keys on the top row, I’ve kept the edges of the keyboard
clear of keys that enter characters. I found it uncomfortable reaching for those
keys while typing. You have to move your wrists to do it, which I felt
undermined the whole reason for choosing the Ergodox. They’re better suited for
less frequently used special keys.</p>

<figure>
 <img alt="
   My completed keyboard fully-assembled and plugged in on a desk.
   It's a split keyboard with the two pieces connected by a cable.
   The keycaps are unlabeled and black." src="/2014/11/16/that-weird-keyboard-done@700x465.webp" />
 <figcaption>
  Behold.
 </figcaption>
</figure>

<p>The Ergodox is a great keyboard, but that almost seems irrelevant. It’s
definitely solved the original problem: no more shooting pains. And typing on it
is really enjoyable. But at the end of the day it’s a fun project that just
happens to produce a nice keyboard as a byproduct.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="hardware" />
      

      
      
        <summary type="html"><![CDATA[At the start of 2014 I started to pay the price for years and years of typing. A horrible RSI was causing shooting pains in my wrists by mid-afternoon each day. Something had to change, but obviously not typing any more wasn’t an option. The solution I settled on was an ergonomic keyboard, specifically the Ergodox.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2014/11/16/that-weird-keyboard-equipment@700x525.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2014/11/16/that-weird-keyboard-equipment@700x525.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Monty Is Wrong</title>
      <link href="https://henry.catalinismith.se/2014/10/20/monty-is-wrong/" rel="alternate" type="text/html" title="Monty Is Wrong" />
      <published>2014-10-20T00:00:00+02:00</published>
      <updated>2014-10-20T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2014/10/20/monty-is-wrong</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2014/10/20/monty-is-wrong/"><![CDATA[<p>Last Friday, at All Your Base 2014, MySQL creator Monty Widenius said some
controversial stuff about women. There was a lot of backlash.</p>

<figure>
 <blockquote class="twitter-tweet" lang="en">
  <p>
   So Monty just went on stage at <a href="https://twitter.com/hashtag/ayb14?src=hash">#ayb14</a> and said that women don&#39;t really &quot;get&quot; hacking.
   Feel like I should get my coat...
  </p>
  &mdash; Lorna Mitchell (@lornajane)
  <a href="https://twitter.com/lornajane/status/523150640747667457">
   October 17, 2014
  </a>
 </blockquote>

 <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
 <figcaption>
  You gobshite Monty.
 </figcaption>
</figure>

<p>Today, in an attempt to address the criticism, an interview was published in
which Monty explained
<a href="http://isit.kajarno.com/2014/10/on-obsession-monty-and-gender-issues.html">his views in more detail</a>.</p>

<blockquote>
  <p>My personal feeling is that men get more easily obsessed with things,
including computers. They forget their surroundings, neglect their families
and friends. And I think that’s more insulting to men than to women, who I
think are better at combining work and having a life. This type of male
obsession I find to be mostly a negative trait, even though single-minded
focus on coding from the early teens, followed by 16 hour coding days at work,
is what has enabled many successful pieces of software.</p>
</blockquote>

<p>If anything, this statement makes matters worse. Instead of retracting and
apologising for his generalisations, Monty has reiterated them and explained
them in more detail. It’s unclear why anybody would think this might make a good
preamble for a heartfelt apology about their sexist remarks, but there you go.
Anyway, here is why I think Monty is wrong.</p>

<p>In 2008, a report called
<a href="http://documents.library.nsf.gov/edocs/HD6060-.A84-2008-PDF-Athena-factor-Reversing-the-brain-drain-in-science,-engineering,-and-technology.pdf">The Athena Factor</a>
was published exploring the many reasons why women leave science, engineering
and tech jobs at such a higher rate than men. It painted a damning picture of a
male-dominated industry structured in a way that systematically disadvantages
women at every turn. It’s worth a read, but here’s the most damning thing from
it. It’s a diagram illustrating the disproportionate rate of attrition among
women in tech.</p>

<figure>
 <img alt="Diagram illustrating a 47% rate of quitting among women in science, 39% in engineering, and 56% in technology." src="/2014/10/20/monty-is-wrong-quit@700x380.webp" />
 <figcaption>
  Slightly confusing visuals here but the key takeaway is that over half of the engineers stop being engineers.
 </figcaption>
</figure>

<p>Right off the bat we’re looking at a very different picture of the world than
the one Monty describes. Rather than a simple pipeline problem (not enough women
coming in), we’re instead seeing women <em>driven out</em> of the industry at a
worrying rate. In fact, the report even suggests that remarks like Monty’s are a
likely contributing factor to this the problem.</p>

<blockquote>
  <p>Another sobering finding is the degree to which women at SET companies believe
that their male colleagues consider females to be intrinsically less capable.
In engineering and technology, more than a quarter of female respondents feel
they are seen as genetically disadvantaged in SET fields. When Larry Summers,
then-president of Harvard University, talked about how women have a “different
availability of aptitude” in his infamous speech of January 2005, he was
perhaps merely giving voice to a standard view!</p>

  <p>Some researchers are convinced that “the societal assumption that women are
innately less able than men” is the foremost factor contributing to women’s
slow advance in SET careers</p>
</blockquote>

<p>As well as driving away existing contributors, Monty’s words also contribute to
the very pipeline problem that they claim to explain. Remarks like these are a
part of the oppressive framework that tells men and women that their lives have
already been predetermined for them by their genetics, and that they should
favour life choices that play to their “natural” strengths.</p>

<blockquote>
  <p>When women are obsessed, I find they tend to focus on social aspects, which is
why there have been much more of an uptake in women in senior management,
rather than among introverted, solitary hackers. <em>(more questionable wisdom
from the interview containing Monty’s apology)</em></p>
</blockquote>

<p>This is bullshit. It makes me angry to imagine some young woman who was at AYB
or maybe read Monty’s interview afterwards and is now questioning whether she’s
“obsessive enough” to contribute to open source projects or otherwise experience
success in her software career. After all, if that really popular clever guy
said it under the legitimising stage lights of a conference venue, couldn’t it
be true? We all suffer from the occasional bout of impostor syndrome. The last
thing any of us needs is leading open source personalities telling anyone their
gender makes them less obsessive and therefore less likely to create great
stuff.</p>

<p>I do believe that there are obstacles for women who want to become big-time open
source developers. But I believe that these obstacles are socially constructed
and thus potentially fixable. Things like:</p>

<ul>
  <li>Outdated social expectations about gender roles discourage young women from
entering the field in the first place.</li>
  <li>Systematic oppression and harassment drives women out of the software industry
at a rate something like twice that of men.</li>
</ul>

<p>That’s the final thing that I hate about this belief that the lack of women in
software can be explained away by these “innate” differences: it absolves the
believer of any responsibility to do anything about it. If it’s predetermined by
nature, then it’s not your problem. Whereas if you dare to look at the issue as
something humans can fix, you might even
<a href="http://wiselit.com/arts-culture/voices/internet-got-godaddy-stop-objectifying-women-sexist-commercials">make a dent in it</a>.</p>

<p>Monty says he feels bad to be thought of as a sexist. Unfortunately, if you
believe in innate differences between men and women that make women less likely
to succeed in software, then you fucking <em>are</em> a sexist. And you will stand
alone as such because the rest of us will not be complicit in our silence.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[Last Friday, at All Your Base 2014, MySQL creator Monty Widenius said some controversial stuff about women. There was a lot of backlash.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2014/10/20/monty-is-wrong-quit@700x380.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2014/10/20/monty-is-wrong-quit@700x380.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">I am not planning any nuclear attacks</title>
      <link href="https://henry.catalinismith.se/2014/08/04/not-planning-any-nuclear-attacks/" rel="alternate" type="text/html" title="I am not planning any nuclear attacks" />
      <published>2014-08-04T00:00:00+02:00</published>
      <updated>2014-08-04T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2014/08/04/not-planning-any-nuclear-attacks</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2014/08/04/not-planning-any-nuclear-attacks/"><![CDATA[<p>I recently wrote about
<a href="/2014/07/27/the-time-i-got-caught-planning-a-nuclear-attack-on-the-usa">a strange phone call that I received</a>
from the letting agents in charge of the house I live in. They’d recently
carried out a routine visual inspection of the house, and were concerned about
my whiteboard diagrams for a game idea I’d been working on.</p>

<p>The phone call ended amicably, and seemed like it put their mind at rest. I
thought I’d heard the end of the matter. I was wrong. Today, they emailed me
informing me that they’ve referred the matter to the local police.</p>

<p>Understandably, being reported to the police as a suspected nuclear terrorist
has come as something of a shock to me. So in the spirit of “If you have nothing
to hide, you have nothing to fear”, here is what was on the whiteboards when
they did their inspection.</p>

<figure>
 <img src="/2014/08/04/not-planning-any-nuclear-attacks-first-whiteboard@700x525.webp" alt="
   Crudely drawn world map drawn in black pen on a whiteboard.
   A red geodesic line originating in Russia ends in a red circle in the USA labelled &quot;Blast Radius&quot;.
   The line is laballed &quot;trajectory&quot;, the red circle is labelled &quot;explosion&quot; and &quot;target&quot;, and the origin is labelled &quot;launch site&quot;." />
  <figcaption>
   This one was for getting a general idea of what the moving parts would be.
  </figcaption>
</figure>

<figure>
 <img src="/2014/08/04/not-planning-any-nuclear-attacks-second-whiteboard@700x525.webp" alt="
   Whiteboard flow chart with three boxes labeled ready, flight and detonated.
   Between the first two is an arrow labelled launch, and between the second two one labelled detonation.
   Below are some vague and barely legible notes.
   One of them says 'repaint trajectory whenever location changes'.
   Another says 'detonate missile if within n km of target'. " />
  <figcaption>
   This one was for figuring out some of the different states and the transitions between them.
  </figcaption>
</figure>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="gamedev" />
      
        <category term="wargames" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[I recently wrote about a strange phone call that I received from the letting agents in charge of the house I live in. They’d recently carried out a routine visual inspection of the house, and were concerned about my whiteboard diagrams for a game idea I’d been working on.]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2014/08/04/not-planning-any-nuclear-attacks-first-whiteboard@700x525.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2014/08/04/not-planning-any-nuclear-attacks-first-whiteboard@700x525.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">The time I got caught planning a nuclear attack on the USA</title>
      <link href="https://henry.catalinismith.se/2014/07/27/the-time-i-got-caught-planning-a-nuclear-attack-on-the-usa/" rel="alternate" type="text/html" title="The time I got caught planning a nuclear attack on the USA" />
      <published>2014-07-27T00:00:00+02:00</published>
      <updated>2014-07-27T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2014/07/27/the-time-i-got-caught-planning-a-nuclear-attack-on-the-usa</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2014/07/27/the-time-i-got-caught-planning-a-nuclear-attack-on-the-usa/"><![CDATA[<p>I missed a phone call at work from my landlord this week. Landlord phone calls
are scary. “Our rent payment probably hasn’t gone out or something”, I began to
worry. I tend to panic quickly. “Shit, I bet it failed because our bank account
is empty. Somebody’s gotten in and emptied out all our money. Fuck.” Fearing the
worst, I phoned back straight away to check what was up.</p>

<blockquote>
  <p>“Hi there, this is Henry Smith. Did you just try to call?”</p>

  <p>“Ah hi Henry, thanks for calling back. Yes we did indeed.”</p>

  <p>“Is everything alright?”</p>

  <p>“Yes, everything’s absolutely fine.”</p>
</blockquote>

<p>For all of two seconds, I felt better. Everything was absolutely fine. But the
conversation wasn’t over yet.</p>

<blockquote>
  <p>“As I’m sure you’re aware, we performed a visual inspection of the property
last week.”</p>

  <p>“Yeah, sure.”</p>

  <p>“And everything was fine. Absolutely fine.”</p>

  <p>“Ah, good!”</p>

  <p>“Except… the person who did the inspection did have some concerns about one
thing. There were some… whiteboards? And some… drawings on them?”</p>

  <p>“Ah shit! Yeah I totally forgot about those! You mean the nuclear attack
thing, right?”</p>
</blockquote>

<p>It’s hard to know what to say next when a conversation is going this badly. This
was my first time being confronted about suspicious nuclear attack-related
whiteboard diagrams. I decided to come clean.</p>

<blockquote>
  <p>“Yeah, that’s right.”</p>

  <p>“Yeeeeeah… Sorry! You see… It’s for this game I was making! It’s like, a web
thing and it uses Google Maps to simulate a nuclear war.”</p>

  <p>“Ahhh, okay! We kind of thought it had something to do with gaming!”</p>
</blockquote>

<p>As quickly as I had become a suspected international terrorist, I was off the
hook. They were surprisingly cool about it, actually. I apologised for wasting
their time with my stupid diagrams for my stupid project, and they just laughed
it off.</p>

<p>In hindsight, it should have been obvious. If you draw a map of the world on a
whiteboard, a big red geodesic line from Moscow to Washington D.C., and a big
red blast radius on top of D.C., then this is not something to leave lying
around during a routine visual inspection by your landlord. All the
ominous-sounding technical jargon about missile guidance and detonation probably
didn’t help to put them at ease either.</p>

<p>My only worry for the future is that if the whiteboard diagrams were enough to
frighten my landlord, what does GCHQ make of commit messages like “Fire nuke at
Washington DC from Moscow”?</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="gamedev" />
      
        <category term="wargames" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[I missed a phone call at work from my landlord this week. Landlord phone calls are scary. “Our rent payment probably hasn’t gone out or something”, I began to worry. I tend to panic quickly. “Shit, I bet it failed because our bank account is empty. Somebody’s gotten in and emptied out all our money. Fuck.” Fearing the worst, I phoned back straight away to check what was up.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Code I Like: Twig Loaders</title>
      <link href="https://henry.catalinismith.se/2013/07/22/code-i-like-twig-loaders/" rel="alternate" type="text/html" title="Code I Like: Twig Loaders" />
      <published>2013-07-22T00:00:00+02:00</published>
      <updated>2013-07-22T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2013/07/22/code-i-like-twig-loaders</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2013/07/22/code-i-like-twig-loaders/"><![CDATA[<p>The <a href="http://twig.sensiolabs.org/">Twig templating library for PHP</a> is really
quite a nice piece of software. In terms of external quality, it’s stable, open
source, actively maintained, and well documented. I’ve been lucky enough to
spend some time at work exploring its internals from time to time over the last
12 months or so, and I’ve found the internal quality to be equally high. It’s a
pleasure to work with code that has been properly looked after.</p>

<p>One particular aspect of Twig’s design that I think exemplifies its internal
quality is the way in which it loads template source code for compilation.
There’s more to it than just <code class="language-plaintext highlighter-rouge">file_get_contents</code>, and Twig manages that extra
complexity well enough that it makes it look extremely simple.</p>

<h2 id="the-hidden-complexity-of-template-loading">The Hidden Complexity Of Template Loading</h2>

<p>A first iteration of a very basic templating library would probably read
template source code from a file on disk, like so.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>return file_get_contents("$templateName.twig");
</code></pre></div></div>

<p>A mature, general purpose library like Twig, on the other hand, needs to support
its users’ diverse needs. Some people may not want to use the filesystem at all,
because they may already have a string, or even an array of strings, ready for
compilation. There’s no getting away from this kind of complexity, so ideally it
should be engaged head-on as a first class part of the design.</p>

<h2 id="successful-complexity-management-in-twig">Successful Complexity Management In Twig</h2>

<p>Twig rightly treats each different way of loading templates as a separate
concern, with its own class implementing
<a href="https://github.com/fabpot/Twig/blob/v1.13.1/lib/Twig/LoaderInterface.php"><code class="language-plaintext highlighter-rouge">Twig_LoaderInterface</code></a>.
Filesystem interaction happens in
<a href="https://github.com/fabpot/Twig/blob/v1.13.1/lib/Twig/Loader/Filesystem.php"><code class="language-plaintext highlighter-rouge">Twig_Loader_Filesystem</code></a>,
while
<a href="https://github.com/fabpot/Twig/blob/v1.13.1/lib/Twig/Loader/String.php"><code class="language-plaintext highlighter-rouge">Twig_Loader_String</code></a>
serves the use-case of those who just want to compile from a string. These
classes are absolutely tiny, so they’re immediately comprehensible.</p>

<p>As well as keeping Twig’s own internal code tidy, this approach provides a great
deal of flexibility for anybody who needs bespoke template loading behaviour.
Got some bizarre restriction forcing you to store templates in your database?
Just write your own implementation of <code class="language-plaintext highlighter-rouge">Twig_LoaderInterface</code> and inject it into
your <code class="language-plaintext highlighter-rouge">Twig_Environment</code> at instantiation.</p>

<p>You can even chain loaders together using
<a href="https://github.com/fabpot/Twig/blob/v1.13.1/lib/Twig/Loader/Chain.php"><code class="language-plaintext highlighter-rouge">Twig_Loader_Chain</code></a>.
Need to fall back to your crazy database template storage only in the event that
the file isn’t found on disk? Add those two loaders in that order to a
<code class="language-plaintext highlighter-rouge">Twig_Loader_Chain</code> and inject that into your <code class="language-plaintext highlighter-rouge">Twig_Environment</code>.</p>

<p>Twig provides all this flexibility and makes it look easy, and it achieves this
by a ruthless separation of concerns. It’s a minor triumph of careful software
design.</p>

<h2 id="failed-complexity-management-in-smarty">Failed Complexity Management In Smarty</h2>

<p>This may seem like a lot of fuss about nothing. So Twig takes a relatively
simple problem and makes it look simple, what’s the big deal? It’s a big deal to
me because this kind of quality is actually rare.</p>

<p>As a counterexample, look at the approach taken by Twig’s main competitor in the
arena of PHP template libraries: <a href="http://www.smarty.net/">Smarty</a>. The Smarty
equivalent to Twig’s loaders all resides within
<a href="https://github.com/TMSolution/smarty/blob/ecd289cab1f3687d9273eb401ef9bc807f706bbb/distribution/libs/sysplugins/smarty_internal_templatebase.php#L20-L361">one enormous “fetch” method</a>.
It’s 300 lines of inflexible, incomprehensible code. The many repsonsibilities
of this method include: output buffering, caching, cookies, errors, response
headers, and the generation and evaluation of PHP code.</p>

<p>To compile a template from a string in Smarty, you must prefix your template
source code with the magic incantation <code class="language-plaintext highlighter-rouge">string:</code> and pass it as the name of the
template to this “fetch” method. If the fifth parameter - <code class="language-plaintext highlighter-rouge">$display</code> - is true,
then “fetch” also takes charge of cache invalidation and the sending of rendered
template output as part of the response body. In short, this is very untidy code
with an equally messy API.</p>

<h2 id="dependency-injection-done-right">Dependency Injection Done Right</h2>

<p>Twig’s flexibility and cleanliness here is a big win for the
<a href="http://en.wikipedia.org/wiki/Dependency_injection">dependency injection pattern</a>.
It’s a great example of how DI isn’t just about maximising testability, and of
why I think DHH is a little too extreme in
<a href="http://david.heinemeierhansson.com/2012/dependency-injection-is-not-a-virtue.html">his criticism of it</a>.
Twig is a clear example where DI has made the API better, not worse.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$loader = new Twig_Loader_String;
$twig = new Twig_Environment($loader);
echo $twig-&gt;render('Hello !', array('name' =&gt; 'Fabien'));
</code></pre></div></div>

<p>As well as flexibility and code quality, this approach to loading templates
happens to be very amenable to unit testing. Small classes with single
responsibilities and few dependencies usually are easier to test than KLOC
behemoths.</p>

<p>That’s it, really. Twig gets this right, and I think that’s worth a little
praise. Go Twig.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[The Twig templating library for PHP is really quite a nice piece of software. In terms of external quality, it’s stable, open source, actively maintained, and well documented. I’ve been lucky enough to spend some time at work exploring its internals from time to time over the last 12 months or so, and I’ve found the internal quality to be equally high. It’s a pleasure to work with code that has been properly looked after.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Monkeypatches broke my build</title>
      <link href="https://henry.catalinismith.se/2013/04/28/monkeypatches-broke-my-build/" rel="alternate" type="text/html" title="Monkeypatches broke my build" />
      <published>2013-04-28T00:00:00+02:00</published>
      <updated>2013-04-28T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2013/04/28/monkeypatches-broke-my-build</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2013/04/28/monkeypatches-broke-my-build/"><![CDATA[<p>The prevalence of monkey patching as a Ruby development practice is a complete
pain in the arse. Sure, some of Ruby’s expressiveness as a tool for building
DSLs is derived from the ability to monkey patch things like 10.days.ago into
the core classes. Admittedly, I occasionally indulge my sweet tooth at the Rails
all-you-can-eat syntactic sugar buffet. But it’s not my favourite approach to
software development by a long shot.</p>

<p>A couple of months ago I wrote a little about the general awkwardness resulting
from monkey patching. I’ve been burnt worse since then, and now I have a
concrete example which I think supports my point of view quite strongly.</p>

<h2 id="coverallscolorizedcolored-clusterfuck">Coveralls/Colorized/Colored Clusterfuck</h2>

<p>Today I wanted to add a service called Coveralls to ppl’s build process.
Coveralls measures test coverage, provides statistics about test coverage over
time, and supplies a dynamic README badge for advertising your project’s
commitment to thorough testing. It’s a perfect fit for a project like ppl, so I
was keen to get it running.</p>

<p>Take a quick look at the commit which added Coveralls to ppl. It’s a tiny
commit, and seemingly harmless. To my great surprise, it broke the build on
Travis CI. And which test did it break?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Failures:

1) Ppl::Format::Table#to_s should colorize columns if requested
Failure/Error: @table.to_s.should eq "\e[31m12345  \e[0m\e[33mJohn Doe  \e[0m\e[34mjdoe@example.org  \e[0m"

expected: "\e[31m12345  \e[0m\e[33mJohn Doe  \e[0m\e[34mjdoe@example.org  \e[0m"
        got: "\e[0;31;49m12345  \e[0m\e[0;33;49mJohn Doe  \e[0m\e[0;34;49mjdoe@example.org  \e[0m"

(compared using ==)
# ./spec/ppl/format/table_spec.rb:80:in `block (3 levels) in &lt;top (required)&gt;'
</code></pre></div></div>

<p>The design of that test is actually a bit of a minor mistake on my part. The
color adapter probably ought to be mocked out, as this test isn’t really about
the ANSI color escape codes themselves.</p>

<p>On the plus side, this has exposed something very interesting: the escape codes
surrounding the strings have changed subtly. And why is that? Because the
Coveralls gem depends on a string colorization library called colorize. When
Coveralls is loaded by the line require “coveralls”, colorize’s String class
monkey patches overwrite the ones previously made by
Ppl::Adapter::Color::Colored when it loads the colored gem.</p>

<h2 id="what-the-fuck">What. The. Fuck.</h2>

<p>Did you catch that? My project’s test suite failed because one of Coveralls’
dependencies changed the behaviour of Ruby’s core String class in a way that
overwrites changes previously made to that class by a dependency of ppl. This is
absolute chaos. It’s not even anybody’s fault, but rather an inevitable
consequence of monkey patching.</p>

<p>I’m quite new to Ruby, so I have to rely on others for perspective on this
approach to Ruby development. Judging by Avdi Grimm’s 2008 post, “Monkeypatching
is Destroying Ruby”, it was considered quite hip five years ago. Here we are in
2013, and installing a code coverage tool has literally changed the output of my
code. It’s pretty clear that Avdi was right, anyway.</p>

<p>Refinements look set to right some of these wrongs, but Ruby 2.0 is still warm
from the oven, and projects like ppl will probably be supporting 1.9 for a long
time to come. In the meantime, I’ve decided to start treating monkey patches
more or less like a bad idea. Not because they are necessarily fragile in
themselves, but because of the fragility they so inscrutably create in other
code.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devtools" />
      

      
      
        <summary type="html"><![CDATA[The prevalence of monkey patching as a Ruby development practice is a complete pain in the arse. Sure, some of Ruby’s expressiveness as a tool for building DSLs is derived from the ability to monkey patch things like 10.days.ago into the core classes. Admittedly, I occasionally indulge my sweet tooth at the Rails all-you-can-eat syntactic sugar buffet. But it’s not my favourite approach to software development by a long shot.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Debian Squeeze Vagrant Base Box</title>
      <link href="https://henry.catalinismith.se/2012/10/16/debian-squeeze-vagrant-base-box/" rel="alternate" type="text/html" title="Debian Squeeze Vagrant Base Box" />
      <published>2012-10-16T00:00:00+02:00</published>
      <updated>2012-10-16T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2012/10/16/debian-squeeze-vagrant-base-box</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2012/10/16/debian-squeeze-vagrant-base-box/"><![CDATA[<p>In an effort to suck less at dealing with all the various sysadmin-related tasks
that come hand-in-hand with being a web developer, I’m learning my way around
Vagrant and Chef.</p>

<p>One of the many things I’ve been struggling with is the lack of a Debian Squeeze
vagrant box that actually works with my version of VirtualBox.</p>

<p>I’m not sure what the problem is here. One of the most common error messages
complained that the guest additions in the base box were from a different
version of VirtualBox from the one I’m using. If that’s an issue, perhaps it
might be better if the likes of <a href="https://virtualbox.es">virtualbox.es</a> made the
VirtualBox version of each box available.</p>

<p>Thing is, it’s Debian Squeeze or nothing as far as I’m concerned, so the only
option remaining was to build my own base box. The documentation for this is
pretty great, but in the end I had to resort to a lot of Googling to figure
various things out so this blog post attempts to unite all the information I
needed in one place.</p>

<h2 id="1-install-debian-in-virtualbox">1. Install Debian in VirtualBox</h2>

<p>I started by downloading
<a href="http://www.debian.org/releases/squeeze/debian-installer/"><code class="language-plaintext highlighter-rouge">debian-6.0.6-i386-netinst.iso</code></a>
from the Debian homepage. Following
<a href="http://vagrantup.com/v1/docs/base_boxes.html">Vagrant’s base box guidelines</a>, I
created a virtual machine with the following properties:</p>

<ul>
  <li>40GB dynamically resizing drive</li>
  <li>360MB of memory allocation</li>
  <li>Audio disabled</li>
  <li>USB disabled</li>
  <li>NAT networking</li>
</ul>

<p>As per the recommendations, I chose a hostname of <code class="language-plaintext highlighter-rouge">vagrant-debian-squeeze</code> and a
domain of vagrantup.com.</p>

<h2 id="2-install-required-software">2. Install Required Software</h2>

<p>Vagrant requires Ruby, RubyGems, Puppet, Chef and an SSH server to be present on
the machine in order for it to work as a base box. To install these, I did this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget -O- http://opscode.com/chef/install.sh | bash
</code></pre></div></div>

<p>This is easier and more reliable than going through all the steps manually.</p>

<h2 id="3-remove-unwanted-software">3. Remove Unwanted Software</h2>

<p>Debian helpfully detects that you’re installing it in VirtualBox and installs a
bunch of related packages for you. We don’t want them though, and they have to
be removed in order to install the proper guest additions.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get remove --purge virtualbox-ose-guest-x11
apt-get autoremove
apt-get remove --purge virtualbox-ose-guest-utils
</code></pre></div></div>

<p>This one was a pain to figure out. Much thanks to
<a href="http://revcode.wordpress.com/2012/02/25/uninstalling-the-default-virtualbox-ose-guest-additions-on-debian/">a blog post on revcode.wordpress.com</a>
for the life-saving Google search result.</p>

<h2 id="4-install-guest-additions">4. Install Guest Additions</h2>

<p>There’s a button in VirtualBox that effectively inserts the guest additions as
an imaginary CD-ROM. After pressing that, the commands to actually install them
were as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install module-assistant
mount /media/cdrom
sh /media/cdrom/VBoxLinuxAdditions.run
</code></pre></div></div>

<p>After this it politely suggested a reboot, so I did.</p>

<h2 id="5-break-all-security">5. Break All Security</h2>

<p>This next bit felt so wrong. The standard Vagrant base box has a pretty funky
security model in order to make it convenient as a testing environment. The
guidelines stipulate passwordless sudo privileges, which entails installing sudo
and then immediately breaking off the locks.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install sudo sudo
visudo
</code></pre></div></div>

<p>The line I added to /etc/sudoers was %admin ALL=NOPASSWD: ALL, or in English,
“Everyone in the group called ‘admin’ can do anything they want to this box”.
After that, I ran /etc/init.d/sudo restart to make it so.</p>

<h2 id="6-hand-over-the-keys">6. Hand over the keys</h2>

<p>The standard vagrant username is ‘vagrant’, with password ‘vagrant’. I set this
user account up during Debian installation, but there was more to it than that.
The user must be added to the admin group.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>groupadd admin
usermod -G admin vagrant
</code></pre></div></div>

<p>For good measure, I also decided to disable root login with passwd -l root.</p>

<h2 id="7-enable-ssh-access">7. Enable SSH access</h2>

<p>In order to be able to access the vm with vagrant ssh, the guidelines recommend
using their standard
<a href="http://github.com/mitchellh/vagrant/tree/master/keys/">insecure SSH key pair</a>
which vagrant will use by default when connecting.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install openssh-server
wget https://raw.github.com/mitchellh/vagrant/master/keys/vagrant.pub
mkdir /home/vagrant/.ssh
chmod 644 vagrant.pub
mv vagrant.pub /home/vagrant/.ssh/authorized_keys
</code></pre></div></div>

<p>The line <code class="language-plaintext highlighter-rouge">AuthorizedKeysFile %h/.ssh/authorized_keys</code> was added to
<code class="language-plaintext highlighter-rouge">/etc/ssh/sshd_config</code> to allow passwordless SSH access to take effect after a
quick <code class="language-plaintext highlighter-rouge">/etc/init.d/ssh</code> restart.</p>

<h2 id="8-export-box">8. Export box</h2>

<p>With everything configured correctly, the next step was to turn off the VM and
export it.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vagrant package --base debiansqueeze squeeze32.box
</code></pre></div></div>

<p>The name debiansqueeze there is what I named the VM itself in VirtualBox.</p>

<h2 id="thats-it">That’s It</h2>

<p>After that the box was ready to roll. Just gotta add it to Vagrant.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vagrant box add squeeze32 ~/squeeze32.box
</code></pre></div></div>

<p>From here on out it’s plain old Vagrant usage. Hopefully all this effort will
pay off in the long run when I can sit back and push systems changes to
production with a beer in my hand.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="devops" />
      

      
      
        <summary type="html"><![CDATA[In an effort to suck less at dealing with all the various sysadmin-related tasks that come hand-in-hand with being a web developer, I’m learning my way around Vagrant and Chef.]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">switch (true)</title>
      <link href="https://henry.catalinismith.se/2012/03/17/switch-true/" rel="alternate" type="text/html" title="switch (true)" />
      <published>2012-03-17T00:00:00+01:00</published>
      <updated>2012-03-17T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2012/03/17/switch-true</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2012/03/17/switch-true/"><![CDATA[<p>A while ago I learned a quirky little programming trick which has occasionally
come in handy. It’s something I picked up from some random blog post about ‘cool
programming tricks’ that I can no longer find. It involves the <code class="language-plaintext highlighter-rouge">switch</code>
statement, which is fairly universal in programming languages and normally looks
like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>switch (name) {
 case 'henry':
  tabWidth = 8
  tabMode  = 'tabs'
  break;
 default:
  tabWidth = 4
  tabMode  = 'spaces'
  break;
}
</code></pre></div></div>

<p>It’s generally used to compare one variable, given in parentheses after the
<code class="language-plaintext highlighter-rouge">switch</code> keyword, against a series of possible matches given in a list of <code class="language-plaintext highlighter-rouge">case</code>
statements. The alternative usage of it that I’ve been very occasionally
employing looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>switch (true) {
 case name.length &gt;= 20:
 case name.match(/\d{3}):
 case name.indexOf('test'):
 case name === 'special_case':
  do_this();
  break;

 default:
  do_that();
  break;
}
</code></pre></div></div>

<h2 id="the-good">The Good</h2>

<p>It looks a little confusing at first glance, but once you understand it, it’s
actually a means of improving readability. If you need a piece of code to test a
very long list of conditions and then do one thing if they all pass or if one of
them fails, then a <code class="language-plaintext highlighter-rouge">switch (true)</code> or <code class="language-plaintext highlighter-rouge">switch (false)</code> can do that for you
without having to write one of those really unsightly over-long <code class="language-plaintext highlighter-rouge">if</code> statements
that wrap across several lines.</p>

<p>It works in exactly the same way as <code class="language-plaintext highlighter-rouge">if (x || y || z)</code>, in that once one of the
cases matches, execution hops past the other conditions and your actual code
starts executing. Consider that
<a href="http://framework.zend.com/manual/en/coding-standard.coding-style.html#coding-standard.coding-style.control-statements.if-else-elseif">the normal alternative</a>
to my above example would look like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if (name.length &gt;= 20
 || name.match(/\d{3}/)
 || name.indexOf('test')
 || name === 'special_case'
) {
 do_this();
} else {
 do_that();
}
</code></pre></div></div>

<p>I think that the <code class="language-plaintext highlighter-rouge">switch (true)</code> pattern can be a very elegant-looking way of
handling large sets of conditions. It kind of forces the statement into either
<a href="http://en.wikipedia.org/wiki/Disjunctive_normal_form">disjunctive normal form</a>
or
<a href="http://en.wikipedia.org/wiki/Conjunctive_normal_form">conjunctive normal form</a>
depending on whether you use <code class="language-plaintext highlighter-rouge">switch (true)</code> or <code class="language-plaintext highlighter-rouge">switch (false)</code>. The <code class="language-plaintext highlighter-rouge">case</code>
keyword is an absolute barrier to operator precedence, so your list of cases is
guaranteed to be either a conjunction or disjunction, and no amount of
parentheses can overcome this simplification. For example, take this slight
variation on the above code snippet:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if (name.length &gt;= 20
 || (name.match(/\d{3}/)
 &amp;&amp; name.indexOf('test'))
 || name === 'special_case')
)
</code></pre></div></div>

<p>Using a <code class="language-plaintext highlighter-rouge">switch (true)</code> statement, it would look like much clearer:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>switch (true) {
 case name.length &gt;= 20:
 case name.match(/\d{3}/) &amp;&amp; name.indexOf('test'):
 case name === 'special_case':
}
</code></pre></div></div>

<h2 id="the-bad">The Bad</h2>

<p>For someone who isn’t familiar with the trick, however, seeing it for the first
time in some code they have to maintain, it probably looks quite quirky and
maybe even a little daunting to change.</p>

<p>It’s also technically a little bit of an abuse of the language constructs. The
<code class="language-plaintext highlighter-rouge">if</code> statement is what you’re <em>supposed</em> to use for doing something <em>if</em>
something else. That’s why it’s called “if”. In some parallel universe I can
imagine myself quietly wondering about the motivations of some colleague who
insisted on using a trick like this.</p>

<p>I wish I had some conclusive point to make at the end of all this, but if
anything it’s that “good code” is not always clear cut. One of the guiding
principles that I try my best to stick to when coding is to <strong>avoid writing
quirky code</strong>. I hate quirky code, where somebody has developed their own
esoteric style in defiance of prevailing standards and refused to conform. Code
style is not about freedom of artistic expression any more than your
pronunciation is. Code has two functions:</p>

<ul>
  <li>Tell a computer what to do and how</li>
  <li>Tell a human what the computer is doing and why</li>
</ul>

<p>I don’t know which order those two belong in, so I’ve used an unordered list.
I’m someone who puts a lot of importance on the second one, probably more so
than most developers because of my previous background in human languages. With
a foreign language the aim is to sound like an authentic native speaker, and
people who flat-out refuse to attempt part of the native accent, such as the
rolled Spanish ‘r’, often don’t realise that this shyness actually draws more
attention to their accent than if they actually gave it a go.</p>

<p>The thing about programming is that each language that you learn comes in two
parts, just like with human languages. All programmers learn the first part: the
grammar and vocabulary. It’s really easy to forget about the second part,
though: learning how to imitate the way
<a href="http://www.kernel.org/doc/Documentation/CodingStyle">native speakers</a> turn that
<a href="http://framework.zend.com/manual/en/coding-standard.html">framework</a> of
<a href="http://nodeguide.com/style.html">rules</a> into actual communication.</p>

<p>This is the main thing that puts me off the <code class="language-plaintext highlighter-rouge">switch (true)</code> pattern. It feels
like using a really uncommon word or phrase just because technically it captures
a meaning well, forgetting that the whole point of a word is to be instantly
understandable by others. It may be kind of elegant semantically, but it kind of
makes you sound like a beginner language learner directly translating an
idiomatic phrase from their own native language.</p>

<h2 id="damnit">Damnit</h2>

<p>On reflection, I think I’m more against this pattern than for it. It’s a shame,
because I’m convinced that it’s a good fit for a certain small set of
programming problems. Consider this post, then, a public apology for writing
weird, quirky code and violating the sacred
<a href="http://en.wikipedia.org/wiki/Principle_of_least_astonishment">principle of least astonishment</a>
. It’s certainly not my only offense by a long shot.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      

      
      
        <summary type="html"><![CDATA[A while ago I learned a quirky little programming trick which has occasionally come in handy. It’s something I picked up from some random blog post about ‘cool programming tricks’ that I can no longer find. It involves the switch statement, which is fairly universal in programming languages and normally looks like this:]]></summary>
      

      
      
    </entry>
  
    <entry>
      

      <title type="html">Planarity</title>
      <link href="https://henry.catalinismith.se/2010/05/11/planarity/" rel="alternate" type="text/html" title="Planarity" />
      <published>2010-05-11T00:00:00+02:00</published>
      <updated>2010-05-11T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2010/05/11/planarity</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2010/05/11/planarity/"><![CDATA[<figure>
 <img src="/2010/05/11/planarity@512x412.webp" alt="Desktop app screenshot showing a green dot and four red dots and a bunch of black lines connecting them all." />
</figure>

<p>My dissertation project at university was to implement this 2D Java puzzle game
called <a href="https://en.wikipedia.org/wiki/Planarity">Planarity</a> where you win by
dragging the dots around until none of the lines connecting them cross. Here’s
100 lines of university student Java for your enjoyment.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">planarity.graphGeneration</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.awt.geom.Line2D</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.awt.geom.Point2D</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Random</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Vector</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">planarity.graph.*</span><span class="o">;</span>

<span class="cm">/**
* This class implements the Edge Addition Graph Generation algorithm
* described in the Design section
*
* @author Henry
*/</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">EdgeAdditionGraphGenerator</span> <span class="kd">implements</span> <span class="nc">GraphGenerator</span> <span class="o">{</span>
  <span class="kd">private</span> <span class="nc">Graph</span> <span class="n">graph</span><span class="o">;</span>
  <span class="kd">private</span> <span class="nc">Vertex</span><span class="o">[]</span> <span class="no">V</span><span class="o">;</span>
  <span class="kd">private</span> <span class="nc">Vector</span> <span class="nc">Vprime</span><span class="o">;</span>
  <span class="kd">private</span> <span class="nc">Edge</span><span class="o">[]</span> <span class="no">E</span><span class="o">;</span>

  <span class="kd">public</span> <span class="nc">Graph</span> <span class="nf">generateGraph</span><span class="o">(</span><span class="kt">int</span> <span class="n">n</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">graph</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Graph</span><span class="o">();</span>
    <span class="no">V</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Vertex</span><span class="o">[</span><span class="n">n</span><span class="o">];</span>
    <span class="nc">Vprime</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Vector</span><span class="o">();</span>
    <span class="c1">//System.out.println("start");</span>
    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">n</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
      <span class="no">V</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Vertex</span><span class="o">(</span><span class="n">graph</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">10</span><span class="o">);</span>
      <span class="n">graph</span><span class="o">.</span><span class="na">addVertex</span><span class="o">(</span><span class="no">V</span><span class="o">[</span><span class="n">i</span><span class="o">]);</span>
    <span class="o">}</span>
    <span class="n">graph</span><span class="o">.</span><span class="na">arrangeAsCircle</span><span class="o">();</span>
    <span class="c1">//System.out.println("here");</span>
    <span class="n">connectTwoRandomVertices</span><span class="o">();</span>
    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">graph</span><span class="o">.</span><span class="na">countEdges</span><span class="o">()</span> <span class="o">&lt;</span> <span class="o">((</span><span class="mi">3</span> <span class="o">*</span> <span class="n">n</span><span class="o">)</span> <span class="err">–</span> <span class="mi">6</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="mi">1000</span><span class="o">;</span> <span class="n">x</span><span class="o">++)</span> <span class="o">{</span>
      <span class="nc">Vertex</span> <span class="n">p</span> <span class="o">=</span> <span class="n">getRandomVprimeVertex</span><span class="o">();</span>
      <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="no">V</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
        <span class="nc">Vertex</span> <span class="n">q</span> <span class="o">=</span> <span class="no">V</span><span class="o">[</span><span class="n">i</span><span class="o">];</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">q</span><span class="o">))</span> <span class="o">{</span>
          <span class="k">continue</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">isAdjacentTo</span><span class="o">(</span><span class="n">q</span><span class="o">))</span> <span class="o">{</span>
          <span class="k">continue</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="n">graph</span><span class="o">.</span><span class="na">addEdge</span><span class="o">(</span><span class="n">p</span><span class="o">,</span> <span class="n">q</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">graph</span><span class="o">.</span><span class="na">countEdgeIntersections</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
          <span class="n">graph</span><span class="o">.</span><span class="na">removeEdge</span><span class="o">(</span><span class="n">p</span><span class="o">,</span> <span class="n">q</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
          <span class="k">if</span> <span class="o">(!</span><span class="nc">Vprime</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">i</span><span class="o">))</span> <span class="o">{</span>
            <span class="nc">Vprime</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
          <span class="o">}</span>
        <span class="o">}</span>
        <span class="k">break</span><span class="o">;</span>
      <span class="o">}</span>
    <span class="o">}</span>
    <span class="nc">Vertex</span><span class="o">[]</span> <span class="n">v</span> <span class="o">=</span> <span class="n">graph</span><span class="o">.</span><span class="na">getVertices</span><span class="o">();</span>
    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">v</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
      <span class="k">if</span> <span class="o">(</span><span class="n">v</span><span class="o">[</span><span class="n">i</span><span class="o">].</span><span class="na">getAdjacentEdges</span><span class="o">().</span><span class="na">length</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">graph</span><span class="o">.</span><span class="na">removeVertex</span><span class="o">(</span><span class="n">v</span><span class="o">[</span><span class="n">i</span><span class="o">]);</span>
      <span class="o">}</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="n">graph</span><span class="o">;</span>
  <span class="o">}</span>

  <span class="kd">private</span> <span class="kt">void</span> <span class="nf">connectTwoRandomVertices</span><span class="o">()</span> <span class="o">{</span>
    <span class="nc">Random</span> <span class="n">r</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Random</span><span class="o">();</span>
    <span class="kt">int</span> <span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">;</span>
    <span class="n">a</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">nextInt</span><span class="o">(</span><span class="no">V</span><span class="o">.</span><span class="na">length</span><span class="o">);</span>
    <span class="k">do</span> <span class="o">{</span>
      <span class="c1">// System.out.println("323");</span>
      <span class="n">b</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">nextInt</span><span class="o">(</span><span class="no">V</span><span class="o">.</span><span class="na">length</span><span class="o">);</span>
    <span class="o">}</span> <span class="k">while</span> <span class="o">(</span><span class="n">a</span> <span class="o">==</span> <span class="n">b</span><span class="o">);</span>
    <span class="n">graph</span><span class="o">.</span><span class="na">addEdge</span><span class="o">(</span><span class="no">V</span><span class="o">[</span><span class="n">a</span><span class="o">],</span> <span class="no">V</span><span class="o">[</span><span class="n">b</span><span class="o">]);</span>
    <span class="nc">Vprime</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">a</span><span class="o">);</span>
    <span class="nc">Vprime</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">b</span><span class="o">);</span>
  <span class="o">}</span>

  <span class="kd">private</span> <span class="nc">Vertex</span> <span class="nf">getRandomVprimeVertex</span><span class="o">()</span> <span class="o">{</span>
    <span class="nc">Random</span> <span class="n">r</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Random</span><span class="o">();</span>
    <span class="kt">int</span> <span class="n">num</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="nc">Vprime</span><span class="o">.</span><span class="na">elementAt</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">nextInt</span><span class="o">(</span><span class="nc">Vprime</span><span class="o">.</span><span class="na">size</span><span class="o">())).</span><span class="na">toString</span><span class="o">());</span>
    <span class="k">return</span> <span class="no">V</span><span class="o">[</span><span class="n">num</span><span class="o">];</span>
  <span class="o">}</span>

  <span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">canConnect</span><span class="o">(</span><span class="nc">Vertex</span> <span class="n">p</span><span class="o">,</span> <span class="nc">Vertex</span> <span class="n">q</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Edge</span><span class="o">[]</span> <span class="n">edges</span> <span class="o">=</span> <span class="n">graph</span><span class="o">.</span><span class="na">getEdges</span><span class="o">();</span>
    <span class="nc">Line2D</span> <span class="n">pq</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Line2D</span><span class="o">.</span><span class="na">Double</span><span class="o">();</span>
    <span class="n">pq</span><span class="o">.</span><span class="na">setLine</span><span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">getCenterX</span><span class="o">(),</span> <span class="n">p</span><span class="o">.</span><span class="na">getCenterY</span><span class="o">(),</span> <span class="n">q</span><span class="o">.</span><span class="na">getCenterX</span><span class="o">(),</span>
    <span class="n">q</span><span class="o">.</span><span class="na">getCenterY</span><span class="o">());</span>
    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">edges</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
      <span class="nc">Line2D</span> <span class="n">e</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Line2D</span><span class="o">)</span> <span class="n">edges</span><span class="o">[</span><span class="n">i</span><span class="o">];</span>
      <span class="k">if</span> <span class="o">(!</span><span class="n">pq</span><span class="o">.</span><span class="na">intersectsLine</span><span class="o">(</span><span class="n">e</span><span class="o">))</span> <span class="o">{</span>
        <span class="k">continue</span><span class="o">;</span>
      <span class="o">}</span>
      <span class="nc">Point2D</span> <span class="n">p1</span><span class="o">,</span> <span class="n">p2</span><span class="o">,</span> <span class="n">p3</span><span class="o">,</span> <span class="n">p4</span><span class="o">;</span>
      <span class="n">p1</span> <span class="o">=</span> <span class="n">pq</span><span class="o">.</span><span class="na">getP1</span><span class="o">();</span>
      <span class="n">p2</span> <span class="o">=</span> <span class="n">pq</span><span class="o">.</span><span class="na">getP2</span><span class="o">();</span>
      <span class="n">p3</span> <span class="o">=</span> <span class="n">pq</span><span class="o">.</span><span class="na">getP1</span><span class="o">();</span>
      <span class="n">p4</span> <span class="o">=</span> <span class="n">pq</span><span class="o">.</span><span class="na">getP2</span><span class="o">();</span>
      <span class="k">if</span> <span class="o">(</span><span class="n">p1</span><span class="o">.</span><span class="na">distance</span><span class="o">(</span><span class="n">p3</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="k">continue</span><span class="o">;</span>
      <span class="k">if</span> <span class="o">(</span><span class="n">p1</span><span class="o">.</span><span class="na">distance</span><span class="o">(</span><span class="n">p4</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="k">continue</span><span class="o">;</span>
      <span class="k">if</span> <span class="o">(</span><span class="n">p2</span><span class="o">.</span><span class="na">distance</span><span class="o">(</span><span class="n">p3</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="k">continue</span><span class="o">;</span>
      <span class="k">if</span> <span class="o">(</span><span class="n">p2</span><span class="o">.</span><span class="na">distance</span><span class="o">(</span><span class="n">p4</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="k">continue</span><span class="o">;</span>
      <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="gamedev" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2010/05/11/planarity@512x412.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2010/05/11/planarity@512x412.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">Alpine Storage</title>
      <link href="https://henry.catalinismith.se/2009/08/11/alpine-storage/" rel="alternate" type="text/html" title="Alpine Storage" />
      <published>2009-08-11T00:00:00+02:00</published>
      <updated>2009-08-11T00:00:00+02:00</updated>
      <id>https://henry.catalinismith.se/2009/08/11/alpine-storage</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2009/08/11/alpine-storage/"><![CDATA[<figure>
 <img src="/2009/08/11/alpine@800x600.webp" alt="Forklift truck in the foreground on a runway with a warehose in the background" />
</figure>

<p>Alpine’s website was the first thing I ever got paid to make on the computer. I
even did the photography myself, in between unloading containers and filing
paperwork. I still have an
<a href="https://henry.catalinismith.com/alpinestorage.co.uk/">archived version of the site</a>
on here.</p>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2009/08/11/alpine@800x600.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2009/08/11/alpine@800x600.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
    <entry>
      

      <title type="html">University Group Project</title>
      <link href="https://henry.catalinismith.se/2009/03/16/university-group-project/" rel="alternate" type="text/html" title="University Group Project" />
      <published>2009-03-16T00:00:00+01:00</published>
      <updated>2009-03-16T00:00:00+01:00</updated>
      <id>https://henry.catalinismith.se/2009/03/16/university-group-project</id>
      
      
        <content type="html" xml:base="https://henry.catalinismith.se/2009/03/16/university-group-project/"><![CDATA[<figure>
 <img src="/2009/03/16/university-group-project@256x256.webp" alt="Safari screenshot showing a webpage loaded on localhost with a placeholder avatar for a Dr. Ravi on the left and a list of appointments for Thursday 12th March on the right. The patients are all named after pro skaters such as Tony Hawk and Danny Way, and the illnesses are Theme Hospital illnesses such as bloaty head and invisibility." />
</figure>

<p>A bunch of us built a medical appointment scheduling app together at university.
It’s some of the oldest code I wrote that I still have a copy of. For
nostalgia’s sake, here’s the contents of <code class="language-plaintext highlighter-rouge">doctor.php</code> as seen in the address bar
above.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//include 'includes/common.php';</span>

<span class="nv">$doctorID</span> <span class="o">=</span> <span class="nv">$user</span><span class="o">-&gt;</span><span class="nf">getDoctorID</span><span class="p">();</span>

<span class="nv">$date</span>     <span class="o">=</span> <span class="k">isset</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="s1">'date'</span><span class="p">])</span> <span class="o">?</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s1">'date'</span><span class="p">]</span> <span class="o">:</span> <span class="no">DEBUG_DATE</span><span class="p">;</span>

<span class="nv">$res1</span> <span class="o">=</span> <span class="nv">$dbs</span><span class="o">-&gt;</span><span class="nf">query</span><span class="p">(</span>
	<span class="s1">'SELECT
		Users.lastName
	FROM
		Doctors,
		Users
	WHERE
		Doctors.userID = Users.userID
		AND Doctors.doctorID = %d'</span><span class="p">,</span>
	<span class="nv">$doctorID</span>
<span class="p">);</span>

<span class="nv">$row</span> <span class="o">=</span> <span class="nv">$res1</span><span class="o">-&gt;</span><span class="nf">getNextRow</span><span class="p">();</span>
<span class="nv">$doctorName</span> <span class="o">=</span> <span class="nv">$row</span><span class="p">[</span><span class="s1">'lastName'</span><span class="p">];</span>
<span class="nv">$page</span><span class="o">-&gt;</span><span class="nf">addData</span><span class="p">(</span><span class="s1">'doctorName'</span><span class="p">,</span> <span class="nv">$doctorName</span><span class="p">);</span>

<span class="nv">$res2</span> <span class="o">=</span> <span class="nv">$dbs</span><span class="o">-&gt;</span><span class="nf">query</span><span class="p">(</span>
	<span class="s1">'SELECT
		Appointments.appointmentID,
		Appointments.appointmentType,
		Appointments.appointmentDate,
		Users.firstName,
		Users.lastName
	FROM
		Doctors,
		Appointments,
		Patients,
		Users
	WHERE
		Doctors.doctorID = Appointments.doctorID
		AND Appointments.patientID = Patients.patientID
		AND Patients.userID = Users.userID
		AND Doctors.doctorID = %d
		AND appointmentDate LIKE \'%s%%\'
	ORDER BY
		Appointments.appointmentDate
		ASC'</span><span class="p">,</span>
	<span class="nv">$doctorID</span><span class="p">,</span>
	<span class="nv">$date</span>
<span class="p">);</span>



<span class="nv">$apps</span> <span class="o">=</span> <span class="k">array</span><span class="p">();</span>
<span class="k">while</span> <span class="p">(</span><span class="nv">$res2</span><span class="o">-&gt;</span><span class="nf">hasNextRow</span><span class="p">())</span> <span class="p">{</span>
	<span class="nv">$row</span> <span class="o">=</span> <span class="nv">$res2</span><span class="o">-&gt;</span><span class="nf">getNextRow</span><span class="p">();</span>

	<span class="nv">$time</span>  <span class="o">=</span> <span class="nb">substr</span><span class="p">(</span><span class="nv">$row</span><span class="p">[</span><span class="s1">'appointmentDate'</span><span class="p">],</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
	<span class="nv">$name</span>  <span class="o">=</span> <span class="nv">$row</span><span class="p">[</span><span class="s1">'firstName'</span><span class="p">]</span> <span class="mf">.</span><span class="s1">' '</span><span class="mf">.</span> <span class="nv">$row</span><span class="p">[</span><span class="s1">'lastName'</span><span class="p">];</span>
	<span class="nv">$appID</span> <span class="o">=</span> <span class="nv">$row</span><span class="p">[</span><span class="s1">'appointmentID'</span><span class="p">];</span>
	<span class="nv">$type</span>  <span class="o">=</span> <span class="nv">$row</span><span class="p">[</span><span class="s1">'appointmentType'</span><span class="p">];</span>

	<span class="nv">$apps</span><span class="p">[</span><span class="nv">$time</span><span class="p">]</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
		<span class="s1">'id'</span>   <span class="o">=&gt;</span> <span class="nv">$appID</span><span class="p">,</span>
		<span class="s1">'name'</span> <span class="o">=&gt;</span> <span class="nv">$name</span><span class="p">,</span>
		<span class="s1">'type'</span> <span class="o">=&gt;</span> <span class="nv">$type</span><span class="p">,</span>
	<span class="p">);</span>
<span class="p">}</span>

<span class="nv">$tstamp</span> <span class="o">=</span> <span class="nf">datetimeToTimestamp</span><span class="p">(</span><span class="nv">$date</span><span class="p">);</span>

<span class="nv">$page</span><span class="o">-&gt;</span><span class="nf">addData</span><span class="p">(</span><span class="s1">'doctorID'</span><span class="p">,</span> <span class="nv">$doctorID</span><span class="p">);</span>
<span class="nv">$page</span><span class="o">-&gt;</span><span class="nf">addData</span><span class="p">(</span><span class="s1">'hours'</span><span class="p">,</span> <span class="k">array</span><span class="p">(</span><span class="s1">'09'</span><span class="p">,</span> <span class="s1">'10'</span><span class="p">,</span> <span class="s1">'11'</span><span class="p">,</span> <span class="s1">'12'</span><span class="p">,</span> <span class="s1">'13'</span><span class="p">));</span>
<span class="nv">$page</span><span class="o">-&gt;</span><span class="nf">addData</span><span class="p">(</span><span class="s1">'minutes'</span><span class="p">,</span> <span class="k">array</span><span class="p">(</span><span class="s1">'00'</span><span class="p">,</span> <span class="s1">'15'</span><span class="p">,</span> <span class="s1">'30'</span><span class="p">,</span> <span class="s1">'45'</span><span class="p">));</span>

<span class="nv">$page</span><span class="o">-&gt;</span><span class="nf">addData</span><span class="p">(</span><span class="s1">'viewDate'</span><span class="p">,</span> <span class="nb">date</span><span class="p">(</span><span class="no">NICE_DATE_FORMAT</span><span class="p">,</span> <span class="nv">$tstamp</span><span class="p">));</span>
<span class="nv">$page</span><span class="o">-&gt;</span><span class="nf">addData</span><span class="p">(</span><span class="s1">'tomorrow'</span><span class="p">,</span> <span class="nb">date</span><span class="p">(</span><span class="s1">'Y-m-d'</span><span class="p">,</span> <span class="nv">$tstamp</span> <span class="o">+</span> <span class="p">(</span><span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">24</span><span class="p">)));</span>
<span class="nv">$page</span><span class="o">-&gt;</span><span class="nf">addData</span><span class="p">(</span><span class="s1">'yesterday'</span><span class="p">,</span> <span class="nb">date</span><span class="p">(</span><span class="s1">'Y-m-d'</span><span class="p">,</span> <span class="nv">$tstamp</span> <span class="o">-</span> <span class="p">(</span><span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">24</span><span class="p">)));</span>

<span class="nv">$page</span><span class="o">-&gt;</span><span class="nf">addData</span><span class="p">(</span><span class="s1">'appointments'</span><span class="p">,</span> <span class="nv">$apps</span><span class="p">);</span>
</code></pre></div></div>]]></content>
      

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      
        
      

      
        <category term="blog" />
      
        <category term="webdev" />
      

      
      
        <summary type="html"><![CDATA[]]></summary>
      

      
      
        
        <media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://henry.catalinismith.se/2009/03/16/university-group-project@256x256.webp" />
        <media:content medium="image" url="https://henry.catalinismith.se/2009/03/16/university-group-project@256x256.webp" xmlns:media="http://search.yahoo.com/mrss/" />
      
    </entry>
  
</feed>
