<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://www.tomoliver.net/</id>
    <title>Tom Oliver's Blog - All updates</title>
    <updated>2025-08-12T10:23:02.441Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <author>
        <name>Tom Oliver</name>
        <email>me@tomoliver.net</email>
    </author>
    <link rel="alternate" href="https://www.tomoliver.net/"/>
    <link rel="self" href="https://www.tomoliver.net/atom.xml"/>
    <subtitle>Personal blog about programming, Linux, and much more</subtitle>
    <logo>https://www.tomoliver.net/logo.svg</logo>
    <icon>https://www.tomoliver.net/favicon.png</icon>
    <rights>All rights reserved 2025, Tom Oliver</rights>
    <entry>
        <title type="html"><![CDATA[This can't be real, can it?]]></title>
        <id>https://www.tomoliver.net/posts/tms</id>
        <link href="https://www.tomoliver.net/posts/tms"/>
        <updated>2025-07-14T12:16:54.021Z</updated>
        <summary type="html"><![CDATA[Fixing the chronic pain in my hands without medicine or surgery. This is my story of recovering from RSI and experiencing the mind-body connection.]]></summary>
        <content type="html"><![CDATA[<p>This article is about typing related pain that I will refer to as RSI<sup><a href="#user-content-fn-0" id="user-content-fnref-0" data-footnote-ref="true" aria-describedby="footnote-label">1</a></sup>. After experiencing RSI in my hands for a long time, I have at last arrived at a place where I understand what caused it, and how I can prevent it in the future. This is a recollection of how I got here. If this article is to be of any use to anyone, it will be to the open minded<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref="true" aria-describedby="footnote-label">2</a></sup> reader.</p>
<h2>Contents</h2>
<ul>
<li><a href="#the-pain">The Pain</a>
<ul>
<li><a href="#it-got-worse">It Got Worse</a></li>
</ul>
</li>
<li><a href="#the-cope">The Cope</a></li>
<li><a href="#the-fear">The Fear</a></li>
<li><a href="#the-desperation">The Desperation</a>
<ul>
<li><a href="#is-this-real">Is This Real?</a></li>
</ul>
</li>
<li><a href="#the-cure">The Cure</a></li>
<li><a href="#the-mechanism">The Mechanism</a></li>
<li><a href="#the-relapse">The Relapse</a>
<ul>
<li><a href="#its-over">It&#x27;s over</a></li>
<li><a href="#its-so-over">IT&#x27;S SO OVER</a></li>
<li><a href="#the-turning-point">The Turning Point</a></li>
<li><a href="#were-back">We&#x27;re back</a></li>
<li><a href="#were-so-back">WE&#x27;RE SO BACK</a></li>
</ul>
</li>
<li><a href="#what-was-really-going-on">What was really going on</a>
<ul>
<li><a href="#the-purpose-of-pain">The purpose of pain</a></li>
<li><a href="#how-this-relates-to-rsi">How this relates to RSI</a></li>
<li><a href="#how-did-i-get-better">How did I get better?</a></li>
</ul>
</li>
<li><a href="#further-reading">Further Reading</a></li>
</ul>
<h2>The Pain</h2>
<p>My RSI started in 2022 when I began to develop a pain in the middle joint of my middle finger on my right hand.</p>
<p><img src="/img/middle-finger-hurt.jpeg" alt="my middle finger hurting" normalWidth="301" normalHeight="400" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAKCAIAAAD3rtNaAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA50lEQVR4nAHcACP/AOfV0PDb1/HX0fTY0Prc1P/p4P/w6QDcz8Xk1M3Lpp7Hj4S9f3z+1c7/9+8A1M/Ms3FrwYd/wGZhwXZx8tDJ5KelAM+6urJLSbQ5PY82PJo+QcmblNyakwCoioCXamOESkiUTEx0PD2YYl3ao6EAKBwQqJSMazMyaTI0iUlKoEZI9snCADQAALlZV4wAIYMZJ6YyPeV1ev/b1wBqPzt8FCRWAA5sABePAyPTaWz/4doA17mtbT47NwAAPwEDZR0iy3x34rKsAObIwh4JADwcGVIXHkIAAIAyMMt8eRQTbHYz3U1mAAAAAElFTkSuQmCC"/></p>
<p>It hurt when typing, but also at various times throughout the day. The pain resolved after taking a break from the computer for a few weeks. Thereafter it tended to come back now and again, leaving me in doubt as to what would trigger it. I remember going to see the Doctor about it but they were not much help, I got prescribed pain killers and some kind of steroid cream if I remember correctly.</p>
<h3>It Got Worse</h3>
<p>In the spring 2023 the symptoms became significantly worse whilst on holiday. It seemed strange at the time since I wasn&#x27;t doing any computer work. From that point on it slowly progressed, eventually leading to a climax by the summer. The worst I remember it getting was when both hands felt swollen, tight, the tendons are clicking, every joint in my hands and fingers creaked as it resisted any movement. My hands would burn when I tried to move them. I had absolutely no strength in either hand at this point. I could only hold a mug by &quot;cupping&quot; it with my palms, holding it by the handle was impossible. At this point typing was absolutely excruciating and out of the question.</p>
<h2>The Cope</h2>
<p>In order to rest my hands, I started to use voice control software to control my computer. The software is called <a href="https://talonvoice.com/">Talon</a> and was actually a lot better than I thought it was going to be, especially since my workflow heavily relies on keyboard shortcuts. Alas, this is not an article on Talon so I will speak no more of it.</p>
<p>Although reasonably effective, voice control software is still quite frustrating to use and sometimes made me question my own sanity. After using it for a while, at some point my voice would go hoarse and it would be painful for me speak... Now what ?</p>
<p><img src="/img/kailh-box-pink.jpg" alt="kailh box pink" normalWidth="301" normalHeight="301" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAACXBIWXMAAAsTAAALEwEAmpwYAAABQUlEQVR4nAE2Acn+AJubpKOjrq2ptrW3wLnEyrS+x6yts5+foZGTk4ODgwCtrri4usTEyNG9qrynX4enX4e1o7avsLafn5+OjowAtbfAxMXQ0eDjwImySQA5SwA5wYetx9DUqKSqlJKUAMHDzc3O2KettZpliXcAX20ATJ90kcrQ2tze6sTI0QDb2+XQ0dt5eYBZJkKic41SADQ/Hi6BgYji5e3x9/8A3uDqxMnLgIKCfXJ4gG55JQAOIA4PX2Bam5ub7/T8ANXX4rS4uIqQgra7tIKGgHZ3cXV6d1ZbV6Cjod7g6gDExdC7vMKrrbGmqaR+gntSVVJYVk5fWkqxtbfHx9EAtbnAr7G7l5mdfoN+ZGZiUlNPYlhNmpB9xMrSxMXQAMHH0bvBy6qvtqy0t7O4v7a7wMLL0dDY4tLY4NLW4N0SuRhmUC/mAAAAAElFTkSuQmCC"/></p>
<p>I then began to think about how I could get back to using my keyboard despite the incredible hand pain. I did some research and found that the softest switches I could get for my keyboard were the Kailh Box Silent Pink switches, which only require 35 grams of force to activate. However even after putting these switches in my keyboard it was still far too painful for me to use. That&#x27;s when I started researching if there were any kind of lighter switches...</p>
<p><img src="/img/stenograph.jpg" alt="stenograph machine" normalWidth="400" normalHeight="400" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAACXBIWXMAAAsTAAALEwEAmpwYAAABQUlEQVR4nAE2Acn+AGlxhnJ6k3R9m3J+nWp0mmx0km10jG10jG10jG1zjABud5tGWYsoPXMJK2MAFU9AT35xd5JocYprcotrcokAdHueHjduAAA6AANEAAA9ABlTdXibf4CYeXyVdneRAG9zj1tpmAAHRQANSAAdVgIqZWZzor22z6+nvJqXrwBscY50gK07T4g+U4pVaKBwgrKGlr6Klbvf0eXTx9kAanSWboO5coGybYS2bYSiZn2LZ2h9VlpkvL7I//b/AHN3kmNwmWh3p1VccF1dWWJlZGRnZExMSEFGVejs9gBWX39RWU1meK1FR1U9OzcsLDBCRUwyOUwiLEenq7kAT1uPVlxIY21tYHGkGCE1HSdCPUhjQEhRUlle1dXbAExXj2Npd2lxVWpycGdyf1ZbW09TPnBxcerq9P///yChg3EAMqM4AAAAAElFTkSuQmCC"/></p>
<p>After much research online I found people talking about stenography machines. These are the special kind of keyboards used by typists in court rooms to produce transcripts. Because stenograph operators need to be able to transcribe speech in real time, they use special keys that are extremely light to push down, much lighter than even the lightest standard keyboard switches. I found out that a similarly light switch can be achieved by replacing the standard springs within the kailh pink switches with the stenograph key springs which require only 20 grams of force required to activate. This made typing significantly easier although my pain level was still very high.</p>
<p><img src="/img/kailh-box-pink-parts.jpg" alt="kailh box pink parts" normalWidth="400" normalHeight="400" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAACXBIWXMAAAsTAAALEwEAmpwYAAABQUlEQVR4nAE2Acn+ANvd5eXl8O3q9PPy+/Xy+/Py+/Hu+O7t9uvo8uPg6wDc3OTm4u719f/w7vLy8vb09Pj29v318vvo6PLi3+kA19ff4+Xtr5uKeX1I6u/s/fv/6OTq5eXr5+Tv3tvlANHR2tHR2Yd+d394YL22rdvd48fMz8XByd7b5drX4ADGyNDX1+CgnKBOQDmLg3/Ip7bPkqXEwsrb2uTPzdYAvL7Fy8vVvbnCioKEvcTGvneNnwAdxJiq0dXdwsDIAK2vtrS2vra4wIiEiK6wtJuYnjsEGZqAjb+/yrKvuwCWmaCVl5+Ji5F8fYVraXJ7eYN/hYyJi5STkpuSlJwAd3iAaWxzW11lTlBaPDtDPDhBREJLTkxWXltlaWx2AElLVTM1PiAeJxIQFgUFDgEABwAAChMQGSIgKDY2PWxGwEC1NNzJAAAAAElFTkSuQmCC"/></p>
<h2>The Fear</h2>
<p>Something in my hands must have been fundamentally damaged. I had overused them and now they had sustained a grave injury. I&#x27;d better not use them much for anything now, rest is what they needed. I took around 6 weeks off to go on another holiday, I thought that a summer holiday free from any computer use would be just the ticket. It was during this time that I developed a different kind of pain. I had developed this very tender spot on the inside of my right hand, between the middle and ring fingers. I first noticed it one day while I was washing my hands.</p>
<p><img src="/img/inside-hand-hurt.jpeg" alt="weird sensation on inside of right hand" normalWidth="301" normalHeight="400" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAKCAIAAAD3rtNaAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA50lEQVR4nAHcACP/AN/NyerZ0+/X0vrl3//28P/28P/48gDXxsPl2NLMo57bsqzfsar+6eP//PcAyMTAuo2Ix6WfyKObyY+I/Off//z3ALKgl7aclbaOhreUjsqYkPvr5f/89wBpQiu0rqa/XVbCR0nFiYb/6ub/9PAAGw8ArXZqtGdjtWZitGxo9cS/xpCIAG5jXKuFea9oZL6AfZdYVLJjYcB1cAC3qJ9xZF6MTEu0bGh+PjqtaWjvw74Av62nJyIbVzItlUtJi0lFq3lwallPAKmdlBIBAG5jX4BPSqVlZOWvqioIAJB2gC0xK7SGAAAAAElFTkSuQmCC"/></p>
<p>Whenever it was touched it gave me a strange numbing/pain that radiated out to the rest of my hand, making me flinch. The thing that would trigger it most though would be the sensation of water against it. So for example, when turning on the shower I would check the temperature by putting my hand into the stream of water, this would be a very strong trigger.</p>
<p><img src="/img/some-time-later.jpg" alt="some time later" normalWidth="480" normalHeight="270" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAIAAAB1kpiRAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAvUlEQVR4nGNgYmFRtAmTN45SNkvwzV5ln1fjUz8lvfGmnl8OEwMDAxMLm6xJsIx+qLptumf5JI/atqi6vdratap2iYzMjAxMTIyKVhHhrRsufv+76uq90K4FD/7/9ylcoOaQCNLNwMAgaxJsGlHm01G/9vbF5Vcvxy9Y7lJRJ21vDZFlULaONXCosUgoD+2f6dc8xT6rwdCkWULfg5GJESStaB5j4FquZp6papGm7Bih7ZhrYtKtbpLLyMQEACQLOFt6WiqlAAAAAElFTkSuQmCC"/></p>
<p>Alas, my 6 weeks of rest had come to a conclusion. I hadn&#x27;t done any typing or even computer use in that time, surely there must have been some healing that happened during that time...</p>
<p>I sit down at my laptop to write this <a href="/notes/2023-08-11-0447">note</a>. Here goes nothing, I gingerly apply pressure to the featherlight keys on my laptop, and... <br/>
<strong>... instant pain</strong>. <br/>
<!-- -->It emanates from my fingertip and courses its way through every nerve up to the base of my palm. <br/>
<em>wtf...</em> <br/>
<em>How is this possible?</em> <br/>
<em>Seriously?</em>
<br/>
<em>Literally no improvement after taking all that rest???</em></p>
<p>This made no sense. The only reasonable conclusion I could draw was that damage was permanent and no amount of rest would help it heal... Still, I thought I would at least get some kind of pain reduction.</p>
<h2>The Desperation</h2>
<p>At this point I had no idea what to do. I tried resting for a long time and there was no recovery, instead there was actually more pain.  I thought to myself, <em>&quot;I can&#x27;t be the only software engineer experiencing this&quot;</em> and so, I started looking in the place where the world&#x27;s software engineers all congregate. <a href="https://news.ycombinator.com/">Hacker news</a>. I remember coming across some mentions of one &quot;Dr. Sarno&quot; and his books. My first thought was, <em>&quot;oh its probably just another guy with a chiropractor degree calling himself a doctor&quot;</em>, as is my cynical nature. But thought to myself <em>&quot;what the hell, I&#x27;m desperate AF&quot;</em> and decided to carry on anyways...</p>
<p>Happily, on closer inspection it seemed that this Sarno guy was legit. I instantly ordered  a hardback<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref="true" aria-describedby="footnote-label">3</a></sup> copy of <a href="https://www.amazon.co.uk/Mind-Body-Prescription-John-Sarno/dp/0446675156">The Mind Body Prescription - John Sarno</a></p>
<p><img src="/img/books/the-mind-body-prescription.jpg" alt="The mind body prescription" normalWidth="304" normalHeight="500" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAKCAIAAAAYbLhkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAxklEQVR4nGNgkGJgUGNgUAEjdQYQl8FNkMFbnMFDnCtNj8GGj8GAiYGhxJAhW5OhUJOh3IghV40hS5nh//OKnLoar/quqOb26KaeyhkTGH5cKIlILpNJabXKamEIbMzq6GEonj3Zo7IrtrnTrbidJay+YWYfw95zMyMbuoLKm4LLmh0L2qasmczw/2nD56OZX47nfDyQ/XZb0v//kxk+HSq42eH7cHbk08Uxl6vd/99vZvj/vO3V+oQ3W9K+Hi96uTbx/+t2AC7uTySdYIlUAAAAAElFTkSuQmCC"/></p>
<p>I read the whole thing over a couple of days and instantly something started to change. It was so subtle that I scarcely believed it myself. The pain was  about 1% more bearable. It was such a slight difference that I didn&#x27;t trust my own perceptions.<br/>
<em>Its probably just wishful thinking/placebo.</em></p>
<p>I didn&#x27;t dare to get my hopes up.</p>
<h3>Is This Real?</h3>
<p>But like the book recommended, I began to read and reread each section, trying to absorb all the information that was on each page. I reread the success stories section and really tried to devote my attention fully to the book as I read it.</p>
<p>The pain continued to recede.</p>
<p><em>This can&#x27;t be real, can it?</em><br/>
<em>Is the act of a reading a book actually somehow reducing my pain?</em><br/>
<em>Isn&#x27;t it just because the act of reading is kind of relaxing?</em><br/>
<em>Am I just bullshitting myself somehow and my pain hasn&#x27;t actually changed?</em></p>
<h2>The Cure</h2>
<p>As recommended by the book, I started to try and use my hands as I did before the pain set in. Instead of treating them like they were made out of wet tissue paper, I started to try and perform all of the actions that I used to do previously. This was very hard. Every time I picked up a cup there was this combination of excruciating pain and fear, <em>&quot;wtf are you doing? your body is telling you that you are in pain!? why are you not stopping?!&quot;</em> this thought would ring deafeningly in my mind before, after, and during any kind of action I performed with my hands. However, I was desperate enough to continue, my hands were already so bad that I wasn&#x27;t risking much by trying this in the case that I damaged them further.</p>
<p>I started typing again, this time using the original pink switches. It was painful but I could feel it getting slightly better every day. After another few weeks I tried a harder switch (an extra 10 grams of activation force), and again I was slowly able to progress.</p>
<p>Fast forward 3 months and the pain had reduced by about 80% I could do most things just about fine, <strong>including typing</strong>.</p>
<h2>The Mechanism</h2>
<p>According to Dr. Sarno, many types of chronic pain are <em>not</em> caused by any kind of physical abnormality or tissue damage. Instead it is attributed to what he calls &quot;unconscious repressed rage&quot;. How does that cause pain you ask? The theory goes that the unconscious mind will repress emotions in order to protect the conscious part of it<sup><a href="#user-content-fn-4" id="user-content-fnref-4" data-footnote-ref="true" aria-describedby="footnote-label">4</a></sup> from experiencing them. It will instead distract you by creating the sensation of pain. He calls this TMS<sup><a href="#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref="true" aria-describedby="footnote-label">5</a></sup>.</p>
<p>Yes you read that correctly. According to Sarno, there was no damage to my hands, the pain was caused by my mind.</p>
<p>I&#x27;ll just let that sink in...</p>
<p>Yeah...</p>
<p>So as you can imagine that is a little bit of a hard pill to swallow. And to be honest at this point I was not really a 100% onboard with the whole repressed unconscious rage thing...</p>
<p><em>I mean really?</em><br/>
<em>Do I really have all this pent up rage within me?</em><br/>
<em>I&#x27;d like to think that I didn&#x27;t, but how can I be sure when its all unconscious?</em></p>
<p>I will expand more on the mechanism later on, but before that there is more to my story.</p>
<h2>The Relapse</h2>
<p>So my recovery kind of stopped at getting 80% better. The last 20% of the pain remained, along with that weird sensation on my palm. This was the state of play all the way until the end of 2024, when my symptoms suddenly worsened again to the point that I couldn&#x27;t even hold a fork.</p>
<h3>It&#x27;s over</h3>
<p>It was so sudden and dramatic that it made me doubt myself. It made me think that the partial healing I had experienced before wasn&#x27;t real, it made me think that I had just brainwashed myself and <em>actually</em> there <em>is</em> something structurally wrong with my hands. I began to despair. I started researching again. I started looking at all these hand exercises I could try to help with the pain. I started trying to squeeze a stress ball to improve the muscles in my hands.</p>
<p>This made all my pain much worse. I started spending a lot of time on places like the subreddit for carpal tunnel syndrome. Then I started wearing wrist braces to prevent my wrists from bending and causing further damage. I became obsessed with comparing my hands to other people&#x27;s to see if they looked any different. I started thinking that my hand muscles had irreversibly wasted away and I would never return to doing normal activities even if the surgery worked.</p>
<p>After being on that subreddit for a while I concluded that my situation was hopeless, surgery would be the only option, and even then it did not guarantee a sustained recovery. As you can imagine, this thought was quite depressing.</p>
<h3>IT&#x27;S SO OVER</h3>
<p>I was now firmly back in the <code>catastrophizing</code> stage of my journey. I started questioning everything.</p>
<p><em>Why did I bother becoming a software engineer?</em><br/>
<em>Why did I study computer science at university?</em></p>
<p>But why stop there? Every single life decision was now up for grabs!</p>
<p><em>Everything I ever did was wrong...</em><br/>
<em>I&#x27;m a fuckup...</em></p>
<h3>The Turning Point</h3>
<p>This is when I decided to have another look at some of Dr. Sarno&#x27;s work, just for the hell of it. &quot;<em>I may as well try his technique again, I have no other choice</em>&quot;. But this time instead of just rereading the book of his I had, I did some more research and found the <a href="https://www.tmswiki.org/forum/">TMS forum</a>. This is where fellow sufferers come to ask questions and help others. I realised that there is actually a lot more out there than Dr. Sarno&#x27;s work, the field has advanced since his day. I started looking on the forum for success stories involving hand pain / RSI / Carpal Tunnel. And immediately, just like before, I could feel something changing in my hands whilst I read along. It wasn&#x27;t as if it instantly got better or the pain just went away, but there was certainly a different quality of the sensation I was experiencing, there was a &quot;lightness&quot; is how I would describe it. So I dared to dream, what if this could work again,  and not just work again, maybe I could make some more sustainable changes to my life that allowed me to experience less pain for the foreseeable future too...</p>
<p><img src="/img/over-back-ying-yang.png" alt="so-over-so-back" normalWidth="400" normalHeight="399" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABKUlEQVR4nE2QseqCYBTFr9DgELQ01OIbKBg5FATuvUGjs1ObODjmA4QQhCBUgw/gIDRKUDQGplNL4aDVE5z47p/if+HjHvh+957DpTAMsVqtkKYp7vc7vrVcLmFZFjabDQRDAjoejz/gcrnger2yHo1GME0Tu90OlCQJ/pfYMpvNWDuOAyLCfD4H3W435HmO8/mM9/uNqqpQ1zWD4/GYQVVVQWVZIssy7Pd7tny9Xtxt24YkSZBlGZ1OB/R4PPB8PtE0DX+2Wi3eoigKdF2HYRjodrugoijYRmQVwGAwwHA4RLvdxnQ6Rb/fh6ZpoDiOGfR9n8Eoiv7OQfR7ruuCgiDA4XDA6XRi228uAQgtrrDdbkFiWsDCerFYcLZer4fJZALP8xhar9f4ADbn/3nSuUDHAAAAAElFTkSuQmCC"/></p>
<h3>We&#x27;re back</h3>
<p>So I bought a bunch more modern books<sup><a href="#user-content-fn-5" id="user-content-fnref-5" data-footnote-ref="true" aria-describedby="footnote-label">6</a></sup> and did some more research. It seemed that some people require therapy in order to fully heal from TMS. I was becoming aware that besides just reducing my pain symptoms, there needed to be some change to my psyche if I wanted to have a long lasting recovery. This lead me to researching TMS practitioners in my area using the <a href="https://www.tmswiki.org/ppd/Find_a_TMS_Doctor_or_Therapist">TMS wiki</a>. Unfortunately the website seems quite old and unmaintained, so I didn&#x27;t have much hope of finding someone. Thats when I discovered that there is an organisation in the UK called SIRPA that do a similar thing. <a href="https://www.sirpa.org/find-a-practitioner/">There is even a map that you can use to find someone local to you</a>.</p>
<h3>WE&#x27;RE SO BACK</h3>
<p>In January 2025 I booked a call with <a href="https://www.livingpathway.co.uk">Rachel Smart</a>, a practitioner I found through the SIRPA website. I never looked back. For the first time I was able to talk to someone face to face and get some reassurance that this is actually real!<sup><a href="#user-content-fn-6" id="user-content-fnref-6" data-footnote-ref="true" aria-describedby="footnote-label">7</a></sup> . Unfortunately most people are still not aware of the very clear relationship between mind and body <sup><a href="#user-content-fn-7" id="user-content-fnref-7" data-footnote-ref="true" aria-describedby="footnote-label">8</a></sup>  so when I try to tell  friends &amp; family   about the concept I can sometimes see the fear in their eyes that I have lost it. To be fair, even I thought I had lost it for a while...  Working with someone who knows what they are doing can make all the difference. There is only so much one can do by reading books/articles alone. It can be enough to get the ball rolling, but for me, I needed someone to quell my overthinking and reassure me that I was on the right path.</p>
<p>Fast forward 3 months...
My hands are 90% better and that weird thing on the palm of my hand that I couldn&#x27;t get rid off before is finally gone! Better yet, other problems that I had have also disappeared. For example, I had an intolerance to gluten that started when I was in university, but now I have no problem eating it at all! This was nothing short of a miracle for me! Needless to say my life has been changed and I ain&#x27;t ever going back.</p>
<p>It is still however a ongoing process. My pain has not disappeared without a trace. Its still present in certain situations albeit much diminished in intensity. I don&#x27;t think it is wise or even desirable to make a goal to completely and indefinitely rid oneself of pain. I think the pain acts as a messenger from your body and your unconscious. The intensity of the message can vary greatly, it might be a text message, it might be a fire alarm. Either way it is an invitation to engage with parts of oneself that may not have been given the attention they require. To have the explicit goal to eliminate pain would be to shoot the messenger, which does not go down well in my experience.</p>
<h2>What was really going on</h2>
<h3>The purpose of pain</h3>
<p>To understand it simply we can go back to first principles. What purpose does pain serve? Well, usually it tells you that you are in danger of harming your body if you carry on doing what you are doing. For example, you take something out of the oven and the mittens are not quite thick enough. Ouch! You feel the heat through the mittens and have to frantically put the rice pudding down on the work surface to avoid burning yourself.<br/>
<!-- -->Ok makes sense.<br/>
<!-- -->The pain has definitely served its purpose here.</p>
<h3>How this relates to RSI</h3>
<p>But what has this got to do with the pain I had?</p>
<p>At the time of my pain there were various stressful events going on in my life, some work related, some not. Working was probably the most obvious constant pressure going on. However, I did not do anything about this stress. I had not even noticed it creep up on me. I was putting my body through a really hard time and I was not listening to it. The body does not like being pumped full of adrenaline and being pushed to the extreme all the time. It is not designed for it. But as I was experiencing chronic stress, that is exactly what the body was subjected to.</p>
<p>My body eventually came to conclusion that it was in danger (fair enough), the fight or flight response was constantly being activated without an end in sight. Since from its perspective, <code>work === stress === danger</code>,  it needed to get me to stop working. Since I am a programmer, my job relies heavily on using the keyboard. So, all it needed to do was to make my hands hurt so much that I couldn&#x27;t use the keyboard anymore and then I would be prevented from working, thus eliminating the &quot;danger&quot;. The body has an incredible intelligence and logically chose my hands as the area of my body to experience pain.</p>
<p>Ok, so how come when I took a break from work it didn&#x27;t get better?</p>
<p>Because I was not aware of the mechanism at the time, I believed that my hands were hurting because they had been physically damaged. That is of course a very fearful thought. As I mentioned earlier, fear is the emotion  who&#x27;s purpose is to help us avoid danger. So from the body&#x27;s perspective, I was still in danger. So what reason would there be to let my pain go away?</p>
<h3>How did I get better?</h3>
<p>First of all I needed to acknowledge what my body was trying to tell me all along. Every person experiences stress in their life in one form of another. This can be from interpersonal relationships, finances, illness etc, its not something that can be escaped. The existence of stress is not in and of itself the problem.  The problem was that I was not dealing with the stress I was experiencing in a healthy way.</p>
<p>I was not properly &quot;feeling&quot; my emotions. Its easy to rationalise away why you shouldn&#x27;t be feeling a certain way because of xyz instead of just accepting reality and surrendering to the feeling. It takes time to gain familiarity with the body and to correctly interpret its feelings and to give it space to process emotions. I realised that I was unknowingly repressing emotions instead of dealing with them there and then. No wonder my body was crying out for attention. I needed to teach my body that it was not in danger. That there was no need to be fearful, and that there was nothing structurally wrong with my hands. As you can imagine this was not easy. It took a lot of time and courage. In the end however I was able to get to a place where I am now. I have developed a better relationship with my body and hope that I can continue to live symptom free for the rest of my life.</p>
<p>In terms of the exact exercises I did, I will not elaborate too much as there are great educational resources out there already.
For example:</p>
<ul>
<li><a href="https://www.youtube.com/@PainFreeYou">https://www.youtube.com/@PainFreeYou</a></li>
<li><a href="https://www.youtube.com/watch?v=yPgnM0aUJPs">https://www.youtube.com/watch?v=yPgnM0aUJPs</a></li>
<li><a href="https://www.curablehealth.com/">https://www.curablehealth.com/</a></li>
<li><a href="https://www.tmswiki.org/forum/">https://www.tmswiki.org/forum/</a></li>
</ul>
<p>This article has been pretty long winded but this was my intention all along. If I ever relapse again I will come back here and reread it all and relive every moment. I don&#x27;t ever want to doubt myself again...</p>
<h2>Further Reading</h2>
<ul>
<li><a href="https://www.amazon.co.uk/Mind-Body-Prescription-John-Sarno/dp/0446675156">The Mind Body Prescription - John Sarno</a></li>
<li><a href="https://www.goodreads.com/book/show/130610.The_Divided_Mind?ac=1">The divided mind - John Sarno</a></li>
<li><a href="https://www.goodreads.com/book/show/50021854-the-way-out">The way out - Alan Gordon</a></li>
<li><a href="https://www.goodreads.com/book/show/450534.When_the_Body_Says_No?from_search=true&amp;from_srp=true&amp;qid=PAB53lR0oW&amp;rank=1">When the body says no - Gabor Mate</a></li>
<li><a href="https://www.goodreads.com/book/show/58537332-the-myth-of-normal?ref=nav_sb_ss_1_14">The myth of normal - Gabor Mate</a></li>
</ul>
<h2 class="sr-only" id="footnote-label">Footnotes</h2>
<ol>
<li id="user-content-fn-0">
<p>Whether it was actually RSI or carpal tunnel syndrome or some kind of combination of the two I don&#x27;t know, however in my opinion all such labels are essentially meaningless anyway. I think identifying with them is detrimental to recovery. <a href="#user-content-fnref-0" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-1">
<p>I used to be very close minded, but out of desperation comes a will to try anything. Nothing can open the mind like suffering. <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>Hardbacks on books are like go faster stripes on cars <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-4">
<p>which is the bit of you reading these words <a href="#user-content-fnref-4" data-footnote-backref="" aria-label="Back to reference 4" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p>When he first started out he called it &quot;Tension myositis syndrome&quot;, &quot;myositis&quot; referring to muscle, however at some point he realised that it could also affect other tissues like tendons etc, so he changed it to be &quot;The mindbody syndrome&quot;. <a href="#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 5" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-5">
<p>Full list of books is provided at the end of this article <a href="#user-content-fnref-5" data-footnote-backref="" aria-label="Back to reference 6" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-6">
<p>You know dead internet theory and all that.... <a href="#user-content-fnref-6" data-footnote-backref="" aria-label="Back to reference 7" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-7">
<p>Even calling it a &quot;relationship&quot; implies a separateness, a duality, but they are one and the same <a href="#user-content-fnref-7" data-footnote-backref="" aria-label="Back to reference 8" class="data-footnote-backref">↩</a></p>
</li>
</ol>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <category label="health"/>
        <category label="programming"/>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Coding on E-Ink since 2020]]></title>
        <id>https://www.tomoliver.net/posts/coding-on-eink</id>
        <link href="https://www.tomoliver.net/posts/coding-on-eink"/>
        <updated>2025-04-28T10:37:19.190Z</updated>
        <summary type="html"><![CDATA[Tips I learned programming on e-ink.]]></summary>
        <content type="html"><![CDATA[<p>It was the spring of 2020, I had just received my <a href="https://www.asahi.com/ajw/articles/14469339">アベノマスク (Abe masks)</a>  and I could <em>finally</em> breath easy. Only the people of Japan know the reassurance provided by <em>barely 2 millimetres</em> of coarsely stitched linen, loosely sliding across your nose and mouth  <a href="https://www.youtube.com/shorts/l7xG2efCvyA"> as you board a socially-distanced train</a>. Not a <em>single</em> corona virus would make it through Abe&#x27;s impregnable shield, and <strong>the country knew it</strong>.</p>
<p>A month later the (ultimately to resign and be assassinated) prime minister of Japan had wisely chosen to capitalize on the collective cough of relief brought about by his thought-leading mask policy. He decided to give each and everyone of us ten thousand of his finest yens in order to stimulate the economy. And how should I choose to do my bit for the greater good? I made my first e-ink monitor purchase, namely the <code>Dasung paperlike HD-FT</code>. Although I <em>may have</em> stimulated the Chinese economy instead of Japan&#x27;s, its always the thought that counts when it comes down to <strong>emergency macro-economic stimulus packages</strong>.</p>
<video width="100%" controls=""><source src="/videos/eink.mp4" type="video/mp4"/></video>
<h2>How does it work?</h2>
<p>You&#x27;ve used an e-reader before, right?</p>
<p>Well its basically that but bigger and more responsive.</p>
<p>E-Ink monitors reflect the ambient light in the room in the same way a page in  a book does, thus no backlight is required and much less eyestrain. The &quot;less eyestrain&quot; thing is actually the only benefit to e-ink monitors, they are inferior to conventional monitors in every other way (except maybe power consumption, but honestly who cares?).</p>
<h2>So why do you use E-Ink monitors?</h2>
<p>Because I have no choice
...And due to the downsides that I&#x27;ll get to later, neither should you for you to seriously considering buying one.</p>
<p>I get a headache and my eyes begin to hurt when I look at a conventional screen for too long. If its a really bright screen this might be a really short time like less than 5 seconds. I&#x27;m not sure exactly why this happens but its definitely something to do with having photons blasted into my eyes. I have always been this way since I was a child (in those days we had a CRT). Upon reflection, this may have been a sign from the universe not to pursue a career that involves staring at a computer for 90% of my time. Oops... Luckily e-ink monitors exist and they are a total game changer for people like me. If you feel like you have eyestrain problem then I would definitely recommend trying one out. <a href="https://www.reddit.com/r/eink/">If you&#x27;re lucky you can pick up e-ink monitors for cheap on this subreddit.</a></p>
<h2>The numerous downsides of e-ink technology</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=YcTEymFXw5c">Ghosting</a>
<ul>
<li>In order for an e-ink screen to refresh quickly, it doesn&#x27;t fully reset in the same way as an e-reader. It does a sort of &quot;best effort&quot; refresh which means that you can see a faint afterimage of whatever you happened to have looked at previously. This causes the currently rendered image to appear cloudy and pale. This is known as &quot;Ghosting&quot;.</li>
</ul>
</li>
<li>Limited contrast<!-- -->
<ul>
<li>On a micro level, an e-ink pixel can either be black or white, nothing in between. So how can you have multiple shades of gray with e-ink? This is achieved by dithering, which is where black pixels are interspersed with enough white pixels to cause it to appear grey. But because this introduces noise into the image, the more shades of grey, the more the contrast you lose and the more the quality of the picture deteriorates.
<img src="/img/monalisa.png" alt="My eink monitor degradation" normalWidth="1094" normalHeight="1066" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAABYlAAAWJQFJUiTwAAABiUlEQVR4nB2QXUjTYRjF37rsrlvpQiZBeKMiFkpUUwaOXSjq8mv0HwbiCIcbQ9lgfqCWxtCx/l74OWuYfTjLEErE702xcIhsIs4hoTKI7MIEhUG/8L144DznwDnneUSdouckvirn4jTKv/N9Umd7JBOrnB6H+X0UIhr5iKh5rCUYeMmL1gY+v/dytLdEYKgDl62Wwf5miX09TYiH9+9Qrxgw6HJorC9lcXaYkuJc2lsULGY9em0mddVaRIvdxM7mNL0dz/g00cfK13FsDWWszfmZGOtmVHUR3QoiOt0WSP2UHZOJEOH5N/gH3BzHV0j93efyT4zYjy8IU5VOLlfCr8N1Sb4d6SSxuyCPuzJ45+9G5N+9TSQ0xfflSRmnepp5UlnE9sYUBztzfJtWcdprELfSbtDXZWV+ZoiZyX40mpvcy8sgGPDIb/h67JiqHiGuXRc4HWa8z5sYfuXGajFSWJTNU8UgK6geB6UlBQghBFlZGkYH2qSjw1pJRfkDmfLhdS+2RiM5uen8B2n2DBJWibdoAAAAAElFTkSuQmCC"/></li>
</ul>
</li>
<li>Context dependant settings<!-- -->
<ul>
<li>Using E-ink monitors requires accepting trade-offs. If you are just poking about on the command line you don&#x27;t need a fast refresh rate, and would prefer a higher contrast to make text more readable. If however you are watching a video, you definitely want a high refresh rate and would be willing to sacrifice some contrast to get it. Unfortunately current e-ink monitors do not change these settings automatically, instead you must change the settings via the physical buttons on the monitor. Not particularly ergonomic if you switch contexts frequently.</li>
</ul>
</li>
<li>Slow refresh rate<!-- -->
<ul>
<li>Even on the &quot;fastest&quot; settings the refresh rate is a far cry from conventional monitors.</li>
</ul>
</li>
<li>E-ink Degradation<!-- -->
<ul>
<li>Over time the e-ink panel begins to degrade and show physical defects. Not so good considering how expensive they are! Below is an image of my monitor exhibiting degradation, notice the white lines appearing on what should be a uniformly black background. These e-ink pixels are &quot;stuck&quot; on white and no longer can change when the screen refreshes.
<img src="/img/e-ink-degradation.png" alt="My eink monitor degradation" normalWidth="1152" normalHeight="1042" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAJCAYAAAALpr0TAAAACXBIWXMAABYlAAAWJQFJUiTwAAABXklEQVR4nAXBSygDABzA4f9JcXLhggvKa1wkcVBWLPK6aByEiCnvPEYmbca2hqkJmUdsVlteedTSHGSeS0MeKRcHIicOyuXn++Qz6IR7JzzP8Pdo4cM/xNN2P0F3DyeLbRwvtrM10Yi83+zC3ym83kLgmZ8XO78hG19nY7wFbLydO/A5epG9VRt3vmW+5/y8rNm52Ojg3FnLkauG4/U+Ah4Tw00qZH7KxNWOm5BnmZNtHdcuHVcLnbxbZwk6hvF7DTSXZyHaLi37rgV210wcregJWazcGvU8DBo5MFexaqikSBGJqCsb8EzPcGC2453oZtPcgNdYintEiWegAFt9JoowQUpKNcyOjvLafIlPb8CpU7PUqsRel4mhIo6y9AhSIgWp04yjVippUuWjSogiLVxIFiFOhEQRYkUoyM1AWtos5GQXkhqTQXaxBrPjkPrqWvLio9GpxpgsGyApWvgHT6fkJPvhgdgAAAAASUVORK5CYII="/></li>
</ul>
</li>
<li>No colors!<!-- -->
<ul>
<li><code>Dasung</code> has recently prototyped a color monitor so this may change soon.</li>
</ul>
</li>
</ul>
<h3>The industry leader is totally clueless</h3>
<p>Unfortunately, <code>Dasung</code>, the current industry leader when it comes to e-ink monitors, seems to not grasp the reason people would ever consider buying an e-ink monitor in the first place. The gimmicky features these monitors are being crammed with often do nothing for the average eye strain sufferer, in fact a few of them actually make eyestrain worse. Take a look at this feature from a monitor they call the <a href="https://shop.dasung.com/products/dasung-25-3-e-ink-monitor-paperlike-253">Dasung 253 Dark Knight Version</a>.</p>
<p><img src="/img/dasung-dark-knight.webp" alt="Dasung dark knight mystic neon light feature" normalWidth="790" normalHeight="506" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAIAAAB1kpiRAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAAmklEQVR4nGPYt2/f5MmT8/LyIgvznDLi9f28lG0thWGA4ciRI9++fbt169a5c+dmzJixefPmyWBQV1cHku7o6KioqHB0dNTU1JSQkIDrg+r29/fX0FBRUJAVlxRiYWFgYWHgAwOotLGxsYaGioODTUCgR1S4Z1S4Z3paqIWlga6uJkhaUVFRV1fT0dHRwcHGwtzc0soUjiQkJABXGTJHw3RxCgAAAABJRU5ErkJggg=="/></p>
<p>Ahh yes, the shimmering neon light on this monitor is just what my eyestrain needed!</p>
<h2>Tips I have discovered</h2>
<h3>Get a lamp</h3>
<p>Unless you have a lot of natural light in your room all-year round then you are going to struggle to use your monitor without a lamp of some sorts. Ambient lights are not enough! Some e-ink monitors come with integrated LED frontlights which I advise against using because they:</p>
<ol>
<li>Are a source of eye strain in and of themselves.</li>
<li>Greatly reduce the contrast of the e-ink display.</li>
</ol>
<p>Instead I recommend picking a desk lamp that is tall enough such that it can shine <em>down</em> on the monitor, otherwise you are going to get a lot of reflections. I would also recommend getting a light bulb with a warm hue to minimize blue light exposure (which tends to aggravate eyestrain). Although you can&#x27;t really buy halogen bulbs anymore, it is possible to find LED bulbs that are pretty close substitutes (minus the heat).</p>
<p><img src="/img/lamp-on-e-ink.jpg" alt="A lamp shining on an e-ink monitor" normalWidth="1200" normalHeight="1600" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAIAAAAGpYjXAAAACXBIWXMAAAsTAAALEwEAmpwYAAABBUlEQVR4nAH6AAX/AIdnSIFqS2BuYJF+XZ9+V5d3VpF1VYt0UwCAXT5zWj9MVkxxfGqLdFeUdVaVfV6fjWoAcFE2Y0s0VldJT3BrfXtpnoFlqpV3uaqGAEg2I25QMmlSO3BiTZJ4XaOMcbyqjM29ogA5KBVOLxdOMRZTOyXEvqni2sPKwKiakXwAKCQXIRwPIhYDNSwd3Na///vj/PXaW1hJABwXBwwNBw8CACkaCF9fT4WDbpWTgEdCNQAiHg8bHRUQDghNOSQxIhQqHxIREQYGCAIAMS0dOCweNSMVZEg2PB0SVUYzGhgQAAYAACELAFQFB4QTDHsQDIwVDZMXDUsDBRcKBOK+TsLBmM13AAAAAElFTkSuQmCC"/></p>
<h3>Minimize context switches</h3>
<p>Because messing around with the physical buttons every 5 seconds is obviously a waste of time, you need to keep that to a minimum. To do that, you need to stay on the same settings for as long as possible. For me, I try to stay in the &quot;text&quot; mode most of the time (think high contrast + low refresh rate). To do this I almost exclusively use my keyboard to interact with my computer. This is because accurately positioning a mouse cursor on something you want to click on in a low refresh rate setting is like aiming while peeing drunk. So I try to just use my keyboard. Which naturally leads to us to my preferred e-ink stack:</p>
<h3>The Stack</h3>
<ul>
<li>Linux<!-- -->
<ul>
<li>I have tried e-ink on a Mac and the picture is not as clear as on Linux. I&#x27;m not sure why but I think Macs seem to do some funky Hi-DPI rendering that looks good on a conventional monitor but has little dots everywhere on e-ink.</li>
</ul>
</li>
<li>Tiling window manager (sway)<!-- -->
<ul>
<li>Fractional scaling seems to work pretty well</li>
</ul>
</li>
<li>CLI based workflow</li>
<li>Vim</li>
<li>Vimium extension for your favourite browser
<img src="/img/e-ink-setup-1.jpg" alt="Dasung paperlike hd-ft" normalWidth="1600" normalHeight="1200" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAIAAABPmPnhAAAACXBIWXMAAAsTAAALEwEAmpwYAAABA0lEQVR4nAH4AAf/AFOWy0Ca2EV6upUSIIpteHFdWyIpNRtMXVhRQYOHlQB/ZXBArPJV4/9Njb1wVV6UlZTi3UTa54N+fW1lWmgAgnZ2TK3lQKv1cI2yg4ic1+P7//qS/91he3qDWGaDAGI2KK2Tgtiwfr2he4B6iMy+ye7j4NjM1FpjhR0kLgB6Sy6yinP/9sL/16BzUEWnna3OzOOyud5EVHgLJEYAo8nifYGJfWdfYlBIQCgkk4uqra/fm6riNkdwLWCgAJHJ/WyUyzpKcSIbIBoQDm96opCe2n6QzhorUxkoPwCgtOZoR2tRQVdOExdaAgOGMzKrlJyFcYEfFx0HAAXAi3Ziokl8FAAAAABJRU5ErkJggg=="/>
If you can afford it, having a second e-ink monitor is also great for productivity. You can keep one in text mode and the other in a faster mode for web browsing etc. This will help you to reduce the number of times you have to change the settings on your monitor even more.</li>
</ul>
<h3>Software controls</h3>
<p>In an ideal world we should be able to use software to control the settings of e-ink monitors so that we don&#x27;t have to mess around with any of the physical buttons. Unfortunately my experience with the vendor provided software is pretty bad. I can however recommend <a href="https://github.com/leoluk/paperlike-go">this awesome tool</a> that lets you set the contrast, light and mode on the <code>Dasung paperlike</code>. There is currently an <a href="https://github.com/leoluk/paperlike-go/issues/9">issue open</a> regarding support for the <code>Dasung 253</code>...</p>
<h3>Turn off syntax highlighting</h3>
<p>E-ink does not have good enough contrast for you to discern the differences between many colors the terminal uses. Any text that isn&#x27;t black on white will be difficult to read so I recommend setting your terminal to a monochrome theme. White on black text also has decent contrast but the ghosting is much worse, apparently this can cause the E-ink panel to wear out quicker too. My experience was that at first, the lack of syntax highlighting greatly increased the cognitive load of reading code. After a while though I began to get used to this and now actually prefer it. That is why you may notice that the code snippets on my site do not have any syntax highlighting.</p>
<h2>Conclusion</h2>
<p>To some it up, if you are wondering whether you can be a software developer if you suffer from eyestrain, the answer is yes. Its not always straight forward and can be frustrating at times, but with e-ink its definitely possible. There probably aren&#x27;t many professions that are as text heavy and therefore well suited to e-ink as ours, so it makes sense for adoption to start with us. Who knows where will be in a decade or two...</p>
<p><strong>Good luck!</strong></p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <category label="linux"/>
        <category label="e-ink"/>
        <category label="lifestyle"/>
        <category label="japan"/>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Easily print-debug any text object in Vim]]></title>
        <id>https://www.tomoliver.net/posts/vim-print-debug-text-object</id>
        <link href="https://www.tomoliver.net/posts/vim-print-debug-text-object"/>
        <updated>2024-05-08T14:56:18.375Z</updated>
        <summary type="html"><![CDATA[This is something that I do most days at least once so I made a shortcut for it]]></summary>
        <content type="html"><![CDATA[<p>First things first, dependencies:</p>
<ul>
<li><a href="https://github.com/inkarkat/vim-TextTransform">https://github.com/inkarkat/vim-TextTransform</a></li>
<li><a href="https://github.com/inkarkat/vim-TextTransform">https://github.com/inkarkat/vim-ingo-library</a></li>
</ul>
<p>And add this to your vim config:</p>
<pre lang="vim"><code class="language-vim">
function! LogInline(text)
    return &#x27;console.log(&#x27; . a:text . &#x27;)&#x27;
endfunction

function! LogBelow(text)
    let endPos = g:TextTransformContext[&#x27;endPos&#x27;]
    let lineNumber = endPos[1]
    call append(lineNumber, split(&#x27;console.log(&#x27; . a:text . &#x27;)&#x27;, &quot;\n&quot;))
endfunction

call TextTransform#MakeMappings(&#x27;&#x27;, &#x27;&lt;Leader&gt;l&#x27;, &#x27;LogInline&#x27;)
call TextTransform#MakeMappings(&#x27;&#x27;, &#x27;&lt;Leader&gt;L&#x27;, &#x27;LogBelow&#x27;)

</code></pre>
<h3>Examples</h3>
<p>First lets try doing an inline log.</p>
<pre lang="js"><code class="language-js">&quot;hello world&quot;
</code></pre>
<!-- -->
<pre lang="js"><code class="language-js">console.log(&quot;hello world&quot;)
</code></pre>
<p>Next we&#x27;ll log an existing variable.</p>
<pre lang="js" messages="[object Object]"><code class="language-js">const myvar = &quot;hello world&quot;
</code></pre>
<!-- -->
<pre lang="js"><code class="language-js">const myvar = &quot;hello world&quot;
console.log(myvar)
</code></pre>
<p>It even works on more complex text objects like HTML</p>
<pre lang="js" messages="[object Object],[object Object]"><code class="language-js">&lt;div&gt;
  &lt;p&gt;hello&lt;/p&gt;
&lt;/div&gt;
</code></pre>
<!-- -->
<pre lang="js"><code class="language-js">&lt;div&gt;
  &lt;p&gt;hello&lt;/p&gt;
&lt;/div&gt;
console.log(&lt;div&gt;
  &lt;p&gt;hello&lt;/p&gt;
&lt;/div&gt;)
</code></pre>
<p>Hopefully this saves someone&#x27;s fingers.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <category label="vim"/>
        <category label="linux"/>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[ （日本人） (Japanese People) ]]></title>
        <id>https://www.tomoliver.net/posts/japanese-people</id>
        <link href="https://www.tomoliver.net/posts/japanese-people"/>
        <updated>2024-05-08T14:56:18.367Z</updated>
        <summary type="html"><![CDATA[How do the Japanese see themselves? Is it an accurate reflection of reality or just a western delusion? ]]></summary>
        <content type="html"><![CDATA[<p><img src="/img/japanese-people-book.jpg" alt="picture of the book &#x27;Japanese people&#x27;" normalWidth="2048" normalHeight="1536" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAIAAABPmPnhAAAACXBIWXMAAC4jAAAuIwF4pT92AAAA+ElEQVR4nB3MPUsCAQCA4dfyowaRIMhdLqmEcGmoJSgEkUASAocagqiGvEmtIGyIulGyQBCULhoPwYKCoIISEeksQW1SOJCGSAknIbrAH/A8KHAPanDwI2wu+QzqokHbG2pumytOisANPEF91diWHT+R8XbI/nkw3PAO1ARUI6QgCzloXZ/pPf3vV39PS8/wALfAZT8/h7wU++52tG7v7Tia64MscAUKROAiKn695LWCWoxtZSCDSWYECQ7BB3Fxs1ouFV6rd7sbp5CwTCUn5wjCEszAwqjN754IuF3LVtsa7AuBE/8RK+CBWXCBAE6YhnlYH/PsiI//M79S7yAZdY4AAAAASUVORK5CYII="/></p>
<p>（日本人） (Japanese People) by 橘 玲 (Akira Tachibana)</p>
<p>First a disclaimer...</p>
<ul>
<li>I&#x27;m not Japanese.</li>
<li>I&#x27;m not that good at Japanese.</li>
<li>I&#x27;m not good at reading in general.</li>
</ul>
<p>&quot;Why read a book in Japanese and then write about it English?&quot;</p>
<p>For fun!</p>
<p>The author presents this book as a new theory on what makes Japanese people unique and why. Previous books have attempted to answer the same question, however, the author explains that these are unduly influenced by orientalist (prejudiced outsider-interpretations of the Eastern world) thought.</p>
<p>This is <strong>not</strong> a review!!! It is just some musings on things I found interesting while reading this book...</p>
<h2>Awkward smiles</h2>
<p>The book starts off with a chapter on how in 2011, some power plant technicians appeared to be <strong>smiling</strong> as they delivered some very bad news to the people of Japan. This news being that there was a triple nuclear reactor meltdown happening at Fukushima. Of course they were not smiles of happiness but smiles of a more complex nature (nervousness/guilt/dread...). The book explains how this is not a behaviour unique to Japan but found in lots of Asian countries, most notably in Thailand where there are 13 different categories of smile! This idea is further expanded to show that a lot of what is thought to be typical Japanese behaviour is actually common to many Asian countries with Japan usually being the most watered down version. Somehow, Japan has been convinced (probably due to western influence) that &quot;Japanese&quot; attributes such as 空気を読む (Reading the air) are unique, not to be found anywhere else in the world. But how could this happen...</p>
<p>Time for some trivia!</p>
<h3>What is the national sport of Japan?</h3>
<ul>
<li>Is it Sumo?</li>
<li>Is it Karate?</li>
<li>Is it Pokémon cards?</li>
</ul>
<p>The current national sport of Japan is to compare Japan to the west (specifically America) and lament at how Japan will never catch up to it.</p>
<p>Why does it compare itself to the west and not its neighbours? It might be because until very recently Japan was pretty much the only modern developed economy in Asia and having no worthy adversary in its home court, it naturally looked to the west as a benchmark. During the period I lived in Japan, when meeting someone for the first time I was often asked &quot;Why did you come to Japan?&quot; and then I would give the same old cringe response &quot;Erm... I like Anime and stuff...&quot; and then they would sort of ask in a much more roundabout way than this: &quot;The future is bleak here. Will you be moving back one day?&quot;. After a while I sort of interpreted this as Japanese people not realising that the west is in reality very flawed and Japan does a lot of things way way way better than anywhere else. I hope that as Japan inevitably compares its pandemic response with that of the west&#x27;s it can feel proud of itself for once.</p>
<h2>Don&#x27;t tell me how to live my life!</h2>
<p><img src="/img/1-room-mansion.jpg" alt="layout of a Japanese studio flat" normalWidth="1000" normalHeight="562" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAIAAAB1kpiRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAxUlEQVR4nAG6AEX/APr7+6qzuaevsp6hp62nqcDCrcDArb2/p8TEuuXl5QDU19gAKD01XVocJyQ/ISm5zgCvwwCnvQBcZCZ5d4AA3t7gR0xHoqpmlps6lJJTvc4toK09wtgBkphVhISNAO3r6QIJAkx5g21iRZiUMb3OM5+qPcDUBpSZWIaGkQDv6+MvJQIAOllCRDeZjzKlvQCyxQCluwBcZCZ6eoIA//37r66km6qzqaehtK6dvb6mvb6mvb2iwMC03t7g9j9ivYL8V7YAAAAASUVORK5CYII="/></p>
<p>The book mentions that a family unit quite often can be under the same roof but living very separate lives. It is not unusual for members of a family to eat separately and at different times without the sort of group communication that western families would consider normal. There is also the phenomenon of the ワンルームマンション (studio flat).  This is the default choice for anyone single without kids. Even university students tend not to share a flat. In general, when you are poor and single in the west you economise by sharing rent with housemates. In Japan you just get a smaller studio flat. Personally this suited me just fine, but it does make me wonder if the average foreigner is much more susceptible to loneliness than the average Japanese...?</p>
<h2>Anonymous online</h2>
<p>Facebook never really made the kind of impact in Japan as it did on the west during the late-naughties. Upon registering, Facebook encouraged you to submit your photo, real name, workplace, university, and even political persuasion in order to connect online with your friends. We have gotten a lot more squeamish about privacy these days and the general use case of Facebook has since changed, but at the time a lot of people in the west were more than happy to publicise all this juicy personal information without any coercion. The Japanese (probably wisely) seem to have much more of a natural aversion to making their personal lives public, and, as a consequence Facebook never enjoyed widespread adoption. That is not to say they don&#x27;t use social media, Twitter in particular is pretty popular, however, the vast majority of its users register with a pseudonym. The author makes the case that in the west, the reward of social credit, whether that be reviews on Amazon, retweets, Instagram followers etc. outweighs the risks involved with participating on the internet with one&#x27;s real name. The reverse is true in Japan. The (perceived) risk of humiliation, stalking, or some kind of harassment is too great and so using a nickname became the default.</p>
<h2>The Future</h2>
<p>The author ended the book by saying that Japan is often at the forefront of societal changes that other countries will eventually have to catch up to (aging population, loneliness, slowing economy...), and while this obviously presents massive challenges, it does also mean that Japan has the unique potential to create a new future, one nothing like anything that has come before it.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <category label="book"/>
        <category label="japanese"/>
        <category label="japan"/>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[You don't need a modern computer!]]></title>
        <id>https://www.tomoliver.net/posts/you-dont-need-a-modern-computer</id>
        <link href="https://www.tomoliver.net/posts/you-dont-need-a-modern-computer"/>
        <updated>2024-09-30T14:11:13.772Z</updated>
        <summary type="html"><![CDATA[Using hardware a decade old (2012) as a software developer in 2023]]></summary>
        <content type="html"><![CDATA[<ul>
<li>Are you <code>broke</code>?</li>
<li>Are you <em>trying</em> to heal our <code>damaged</code> planet?</li>
<li>Are you a minimalist <code>hipster</code>?</li>
</ul>
<h2>If you answered &#x27;Yes&#x27; to any of the above...</h2>
<p>... then don&#x27;t buy a new computer! You really don&#x27;t need anything modern to have a great computing experience.
Of course, this is only possible if the hardware in question has been liberated from the shackles of Microsoft/Apple, i.e running Linux. Unfortunately normies usually aren&#x27;t aware of this so they trash their perfectly good hardware without realising its true potential. Think of the amount of e-waste that could have been prevented if Linux on desktop was the norm.... Where is Greta on this? <a href="https://www.penguin.co.uk/authors/141409/greta-thunberg">She likes penguins right?</a></p>
<p>&quot;But I&#x27;m a <em>soy</em>ftware developer! I need 64 CPU cores and 128GB of RAM and 32GB of VRAM and raytracing cores and....&quot;</p>
<p><img src="/img/zoomer-boomer-dad.jpg" alt="boomer using a desktop pc" normalWidth="690" normalHeight="588" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAIAAABPmPnhAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAA9ElEQVR4nAXBsUrDQBgA4MsL+FIKujhHRByymMa1SBDiHhvokDoJ6X7jDaUpIWAweLTkn7IYuHIQOLghULhyEIj8fh9BxLqufd9/XyzSu4fMn8mmOZ3Pf9OEiAQRKaWu63qeV+33v8dj9V0lScI5BwAipdRa53n+kaaICD9fF4Tc314bY/q+JwBgrTXGCCGstatVenN1+TKfj+OotSZCiLqup2kKw/Atija73fNr9Pg0Q8S2bUlZlgDAGMuyTAjBGPtcr5vDARGllIQxppQCAK21UopzPgzDZruVUi6XS9J1HSJqrYuioJQyxgAgjuMgCBzH+QfQiq8oAG/5kwAAAABJRU5ErkJggg==" smallSrc="/img/zoomer-boomer-dad-small.jpg" smallWidth="690" smallHeight="588"/></p>
<p>Depending on what you&#x27;re developing you <em>might</em> need modern hardware, but I&#x27;d like to posit that most developers are web developers, and web applications are usually deployed on some flaccid single-core EC2 instance with 1GB of RAM anyway. Trust me, if you pick your development environment correctly and stay away from bloated IDEs and other soydev hallmarks, you probably wont even be able to tell that you&#x27;re typing on something pre-Trump.</p>
<h3>Other reasons web development works well on old hardware</h3>
<ul>
<li>The web uses JavaScript<!-- -->
<ul>
<li>JavaScript more or less just runs, it doesn&#x27;t require a lengthy CPU intensive compilation step.</li>
</ul>
</li>
<li>Most web frameworks support hot module reload.<!-- -->
<ul>
<li>No need to restart the entire project.</li>
<li>Fewer system resources are needed to provide a tight feedback loop because only code that has changed is reloaded.</li>
</ul>
</li>
<li>The web is <em>supposed</em> to work on old hardware<!-- -->
<ul>
<li>What better way to ensure your site runs on old desktops/entry level mobiles than developing and testing using said hardware?</li>
</ul>
</li>
</ul>
<p>You really don&#x27;t have to give anything up either, with decade old hardware, you should have no trouble finding a GPU with a max resolution of 4K, a motherboard that supports 32GB of RAM, and a CPU with <em>hype</em>rthreading (I stress the hype). Is there really much to gain by shelling out the big bucks on a new workstation?</p>
<h2>What about Laptops tho?</h2>
<p><img src="/img/thinkpad.jpg" alt="ThiccPad meme" normalWidth="1280" normalHeight="720" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAIAAAB1kpiRAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAxUlEQVR4nAG6AEX/AAAADUtX2oiU/yKn/5Ou436UyA542mV/tf/J3kE2OAAACR8RafkXu/8WrP9TSoMEa0MOak9kVn//19w3QUEAABk7Ytj/1///kq/xUktXNGchUD4mNFyelMHtIygyAAkBA2F1toHB/0iL00x1pk16lihliVtslqefuyYlLwASAgY/JFcaUZRAYpRjeZpgdZVZcZR4iaOylJwrJScAEgsRVCIsXhUkaSc7ajZBZCw8Uiw6TTFEjXKFLCYoYvBJ1bkie2YAAAAASUVORK5CYII="/></p>
<p>First of all, laptops suck. They are less powerful, more noisy, and more fragile than desktops, not to mention they are <em>way</em> more expensive! And that&#x27;s without the physiotherapy you&#x27;ll need to correct your neck posture after a decade of using them.</p>
<p>&quot;But my (work/school/oppressive entity) requires that I own a laptop!&quot;</p>
<p>The cold hard reality of life strikes again. Laptops are less straightforward, generally you can&#x27;t upgrade them. The tide might be turning on this however, see <a href="https://frame.work/">framework for example</a>. If you find a framework laptop going for a reasonable price it might well be worth the investment. A safer bet would be to find a Thinkpad on Ebay or FB marketplace (eww), nice and cheaply. No other laptop brand has a better reputation when it comes to longevity and repair-ability than the humble Thinkpad. Unfortunately the modern Thinkpad is not quite as maintenance friendly as its ancestors were, but it is still considered the default choice for people who have principles/value their money. You can even find <a href="https://www.youtube.com/watch?v=La3sb5y7e-k">boomers on youtube</a> who still use the Thinkpad X220 from 2011!</p>
<h3>Notes on software</h3>
<p>I have found things like Electron apps to be pretty bad when it comes to hogging resources. When using old hardware I would say never use Electron based apps when an alternative exists. Mercifully, most of such apps will usually offer a web version which I find to not only use fewer resources, but to be more reliable too.</p>
<p>Leave a webmention about your vintage hardware specs and we can waste time comparing gigahertz and terabytes.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <category label="boomer"/>
        <category label="lifestyle"/>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[How to set NEXT_PUBLIC_* environment variables in Docker]]></title>
        <id>https://www.tomoliver.net/posts/nextjs-docker-public-env-vars</id>
        <link href="https://www.tomoliver.net/posts/nextjs-docker-public-env-vars"/>
        <updated>2024-05-08T14:56:18.369Z</updated>
        <summary type="html"><![CDATA[In this post we explain how it is possible to set NEXT_PUBLIC_* environment variables baked into docker images]]></summary>
        <content type="html"><![CDATA[<p><img src="/img/wojac-next-docker.png" alt="Nextjs and Docker represented by wojacks" normalWidth="939" normalHeight="845" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAJCAYAAAALpr0TAAAACXBIWXMAAC4jAAAuIwF4pT92AAABI0lEQVR4nHWQMUvDUBSF3+hS8I/4Y5xcnKxTp/4DM7hKpVNBcNNJcbAGUVBaC0bqUCVQRfJSEkvSIo1Nm5i0Pe/Iq9DNs53Dd7n3XEGS08mEQRAwiiKuBHDDmqM9SrVxhc7CMKRlWfR9f8XNspSdl0eMgmdtXRHHMQ3DYLVaZa1WY5ZlS/Dp3ePF5QlajWMG45krbNtmsVhkuVxmqVTih+MswbtXh/vntziq1+kN47/V/ylOEqSpvpGuUEoRAD3Po9+THIwTtgc51WKxrKRBAK6ApkjUTRNdu4Pm5wSbVoafaaTzeZ7nVEo5Qk8cVCrc2d5i677B67ND9rpNfn+F1JB+mVKqL5IkkYVCQQoh5JpYl8berrwyT+VN502SdAD0ATz8Av+qOZwsELv/AAAAAElFTkSuQmCC" smallSrc="/img/wojac-next-docker-small.png" smallWidth="828" smallHeight="416"/></p>
<p>Picture the scenario: you have finished developing a <em>web app</em> using your favourite react framework.</p>
<p>Its a pretty simple web app, all it does is display a greeting message.</p>
<p>This greeting message may change depending on which environment you deploy it in, for example, in your development environment you want it to say:</p>
<p>&quot;Hello from development!&quot;</p>
<p>...and when its deployed in your staging environment you want it to say:</p>
<p>&quot;Hello from staging!&quot;.</p>
<p>So naturally you put the greeting message in an environment variable so that you do not need to build a different version of the code for each environment.</p>
<pre><code>// .env
...
NEXT_PUBLIC_GREETING=&quot;Hello from development!&quot;
...
</code></pre>
<h2>Thats too easy tho</h2>
<p>You, being the good <a href="https://www.urbandictionary.com/define.php?term=Soydev">soydev</a> you are, decide to deploy it using docker.</p>
<p>So you write a simple dockerfile to build an image but quickly realise something.</p>
<pre lang="docker" messages="[object Object]"><code class="language-docker">...
RUN npm i
ARG NEXT_PUBLIC_GREETING=&quot;Hello from development!&quot;
RUN next build
...
</code></pre>
<p>Since we have to set this environment variable when we are building the next app, our image will forever have this value hardcoded into its very essence.</p>
<p>This means that even if we try to set the value of the variable at runtime like so:</p>
<pre><code>docker run -e NEXT_PUBLIC_GREETING=&quot;rekt m8&quot; my-image/latest
</code></pre>
<p>It won&#x27;t work because <code>NEXT_PUBLIC_GREETING</code> has already been inlined to be <code>Hello from development!</code> in the compiled code.</p>
<p>So what can we do?</p>
<h2>My solution</h2>
<p>Like many a <a href="https://github.com/vercel/next.js/discussions/17641">brave soul that came before me</a>, my approach to tackling this problem was to set the value of the environment variable to a placeholder that can be switched out at runtime.</p>
<p>To achieve this I created a shell script that can be broken into two parts. The first part is to create the sed commands needed to replace each placeholder with the correct value:</p>
<pre lang="bash" messages="[object Object]"><code class="language-bash">
# Get all the environment variables currently loaded
printenv | \
    # Filter for ones that start with NEXT_PUBLIC
    grep  &#x27;^NEXT_PUBLIC&#x27; | \
    # Replace the = sign with a space
    sed -r &quot;s/=/ /g&quot; | \
    # Feed as arguments to sed so they can be used in a find and replace command
    xargs -n 2 bash -c &#x27;echo &quot;sed -i \&quot;s#APP_$0#$1#g\&quot;&quot;&#x27; 

</code></pre>
<p>Lets test it!</p>
<pre><code>~&gt;&gt; export NEXT_PUBLIC_GREETING=HELLO!
~&gt;&gt; export NEXT_PUBLIC_THEME=Dark
~&gt;&gt; printenv | \
    grep  &#x27;^NEXT_PUBLIC&#x27; | \
    sed -r &quot;s/=/ /g&quot; | \
    xargs -n 2 bash -c &#x27;echo &quot;sed -i \&quot;s#APP_$0#$1#g\&quot;&quot;&#x27;

sed -i &quot;s#APP_NEXT_PUBLIC_GREETING#HELLO!#g&quot;
sed -i &quot;s#APP_NEXT_PUBLIC_THEME#Dark#g&quot;
</code></pre>
<p>As we can see it spits out a <code>sed</code> search and replace command for each environment variable we have.</p>
<p>Now for the second part, to execute these commands against the compiled code files.</p>
<pre lang="bash"><code class="language-bash">#!/usr/bin/env bash

# The first part wrapped in a function
makeSedCommands() {
  printenv | \
      grep  &#x27;^NEXT_PUBLIC&#x27; | \
      sed -r &quot;s/=/ /g&quot; | \
      xargs -n 2 bash -c &#x27;echo &quot;sed -i \&quot;s#APP_$0#$1#g\&quot;&quot;&#x27;
}

# Set the delimiter to newlines (needed for looping over the function output)
IFS=$&#x27;\n&#x27;
# For each sed command
for c in $(makeSedCommands); do
  # For each file in the .next directory
  for f in $(find .next -type f); do
    # Execute the command against the file
    COMMAND=&quot;$c $f&quot;
    eval $COMMAND
  done
done

echo &quot;Starting Nextjs&quot;
# Run any arguments passed to this script
exec &quot;$@&quot;
</code></pre>
<p>Ok cool, we have our script ready! Now how do we use it with docker?</p>
<pre lang="docker" messages="[object Object]"><code class="language-docker">...
ARG NEXT_PUBLIC_GREETING=APP_NEXT_PUBLIC_GREETING
RUN next build
# Copy our script somewhere into the image
COPY entrypoint.sh .
# Make it executable
RUN [&quot;chmod&quot;, &quot;+x&quot;, &quot;/app/entrypoint.sh&quot;]

EXPOSE 3000

ENTRYPOINT [&quot;/app/entrypoint.sh&quot;]

CMD npm run start
</code></pre>
<h2>Caveats</h2>
<p>Lets remind ourselves that we are literally doing a search and replace on some compiled code so don&#x27;t expect <em>everything</em> to be plain sailing.</p>
<p>One thing I have discovered is that because NextJS splits your code into chunks during a production build, and we are simply doing a find and replace on those chunks, obviously the names of those chunks will not change. This means the browser can&#x27;t tell that you have changed the chunks so it will continue to serve the cached version unless you explicitely clear the cache in your browser. I haven&#x27;t had the time to see if there is a workaround for this but presumably just renaming the chunk files without breaking everything somehow would fix this problem.</p>
<p>And voila! Bit of a hacky solution but what can you do?
If you know of a less gross horrible hacky way of doing this then please <a href="/">tell me!</a></p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <category label="docker"/>
        <category label="nextjs"/>
        <category label="react"/>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Responsive Design with Styled Components]]></title>
        <id>https://www.tomoliver.net/posts/responsive-design-with-styled-components</id>
        <link href="https://www.tomoliver.net/posts/responsive-design-with-styled-components"/>
        <updated>2024-05-08T14:56:18.372Z</updated>
        <summary type="html"><![CDATA[In this tutorial we will create some useful shortcuts to make implementing responsive designs easier to implement.]]></summary>
        <content type="html"><![CDATA[<p>First lets make using media queries nicer.</p>
<p>Lets create a file that exports a <code>media</code> object with all the media queries we will need.</p>
<pre lang="tsx" messages="[object Object],[object Object]"><code class="language-tsx">// media.ts
export const desktopWidth = 670

const mq = (strings: TemplateStringsArray, width: number) =&gt;
  `@media only screen and (${
    strings.slice(0, strings.length - 1).join(&quot;&quot;) +
    width +
    strings[strings.length - 1]
  })` as const

export const media = {
  desktop: mq`min-width: ${desktopWidth}px`,
  mobile: mq`max-width: ${desktopWidth - 1}px`,
}
</code></pre>
<p>Import the <code>media</code> object into the file of your component and use like so:</p>
<pre lang="tsx"><code class="language-tsx">// myComponent.tsx
import { media } from &quot;./media&quot;

const MyComponent = styled.span`
  ${media.desktop}{
    background-color: green;
  }
  ${media.mobile}{
    background-color: pink;
  }
`
</code></pre>
<p>Much nicer than typing out a media query every 5 seconds right?</p>
<p>What about hiding/showing components depending on the device size?</p>
<p>Lets add some more to the <code>media.ts</code> file.</p>
<pre lang="tsx" messages="[object Object],[object Object]"><code class="language-tsx">// media.ts
...
export const hideOn = (size: keyof typeof media) =&gt;
  `display: contents;
    ${media[size]} {
      display: none;
    }
  `

const hideOnDesktop = hideOn(&quot;desktop&quot;)

export const HideOnDesktop = styled.div`
  ${hideOnDesktop}
`
</code></pre>
<p>Now we can wrap our components ensuring they are only rendered on the appropriate device:</p>
<pre lang="tsx"><code class="language-tsx">import { media, HideOnDesktop } from &quot;./media&quot;
...
&lt;&gt;
  &lt;HideOnDesktop&gt;
    &lt;MobileOnlyContent /&gt;
  &lt;/HideOnDesktop&gt;
&lt;/&gt;
...

</code></pre>
<p>For more tips on implementing responsive design with styled-components and NextJS, see <a href="/posts/nextjs-styled-components-without-js"> this </a> article.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <category label="css"/>
        <category label="nextjs"/>
        <category label="react"/>
        <category label="styled-components"/>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Next.js with and without JavaScript]]></title>
        <id>https://www.tomoliver.net/posts/nextjs-styled-components-without-js</id>
        <link href="https://www.tomoliver.net/posts/nextjs-styled-components-without-js"/>
        <updated>2024-05-08T14:56:18.370Z</updated>
        <summary type="html"><![CDATA[How to make sure your Next.js site rehydrates correctly, loads fast, reacts responsively, and works for users who disable JavaScript]]></summary>
        <content type="html"><![CDATA[<p>Wait, did you say <em>without</em> JavaScript?<br/>
<!-- -->We are React developers, why should <em>we</em> care about making sites that work without JavaScript?</p>
<p>TLDR;</p>
<ul>
<li>Everyone sees your bare HTML at some point! (Not just users with JavaScript disabled)</li>
<li>Performance (especially on lower-end devices with a slow network)</li>
<li>Cumulative page layout shift</li>
<li>SEO</li>
</ul>
<h2>How does Next.js really work?</h2>
<h3>Static Site Generation (SSG)</h3>
<p>If you are using Next.js (pages router), then its likely that the excellent SSG and SSR features were one of the reasons that made you consider it for your project. Specifically I would like to talk about SSG in this post.</p>
<p>Basically if you don&#x27;t know, when you run <code>next build</code>, Next.js will try to pre-render as much of each page as possible. This is what gets sent to your users when they first access the site. Since there is already some content thats been pre-rendered, React only needs to rehydrate whats already there. This means there is less work done by your browser which translates to  quicker execution times and better user experience.</p>
<p>But there is a much bigger reason that SSG pages appear to load quicker. The secret lies in the sheer magnitude of the JavaScript payload that most sites require. This bundle will include:</p>
<ul>
<li>your code</li>
<li>React</li>
<li>Next.js</li>
<li>all of the other crap in your <code>package.json</code></li>
</ul>
<p>The size of this bundle will dwarf the pre-rendered HTML. As an example of bundle sizes, for any given page on this blog you are reading, the JavaScript bundle will be roughly 10 times the size of the static HTML.</p>
<p>In a traditional client rendered SPA, the <code>index.html</code> file contains almost zero markup. Usually it will just have an empty <code>&lt;div id=&quot;root&quot;&gt;&lt;/div&gt;</code> that React will append DOM nodes on to. This means most (if not all) of the JavaScript needs to be downloaded before anything can be rendered at all. By pre-rendering the HTML, the browser can display something without having to wait for any JavaScript, this can improve the first contentful paint (<a href="https://web.dev/fcp/">FCP</a>). Using this tactic Next.js lets us get away with having large bundle sizes whilst delivering the user a decent experience.</p>
<h3>Responsive SSG</h3>
<p>Although for the most part Next.js takes care of SSG for us, it does require some careful thought when we implement responsive design. One thing to consider with SSG is that everyone gets sent the same static HTML. Whether you&#x27;re on desktop or mobile you will be given the same initial DOM. This can be a problem if you rely on JavaScript to make your website responsive. Lets say your site uses a hook like <code>useWindowSize()</code>, and depending on the width of the window you will render either the desktop layout or the mobile layout. Well this can only happen after the JavaScript bundle has been fetched and React has rehydrated the page. By which time the user will have already seen the bare static HTML. And so will potentially experience a brief flicker as the correct layout is rendered (probably with a few rehydration errors). One solution could be to simply not render anything until rehydration has finished. But of course that defeats the object of SSG in the first place.</p>
<p>My advice is to rely solely on CSS for your responsivity (via media queries). This way <strong>both</strong> the desktop layout and mobile layout will be present in the initial HTML that gets sent out to every device.</p>
<p><em>But wouldn&#x27;t this make my HTML file bigger?</em></p>
<p>Yes. But not by that much. You have to remember that the JavaScript bundle will usually be <em>at least</em> an order of magnitude bigger.</p>
<h3>Progressive Design Enhancement</h3>
<p>Saying all that, if you are a fan of rather creative designs that require dynamically calculating the positions of elements (like using <code>getBoundingClientRect()</code> for example), you must also further consider how your designs will work without the presence of JavaScript. For example on this blog I often use an annotation component that draws a line connecting a sliver of code with a message. For this I need to use JavaScript to get the position of elements as they depend on the screen size of the end device and therefore are impossible to know beforehand. To solve this, I have another design which does not require any JavaScript to work properly. I simply assign a number in the top left corner of each message bubble that corresponds to the number given to the code segment it refers to. This is the design that the static HTML ships with before ultimately it gets rerendered on the client (assuming JS is enabled!). If I simply excluded the annotations entirely from the initial SSG&#x27;d HTML, when the JavaScript eventually <em>did</em> load they would be freshly inserted into the page causing layout shift. This is bad for UX, SEO and all manner of things. Not to mention, some people (who can blame them) browse with JavaScript disabled so they won&#x27;t be able to see anything at all! For these reasons its worth thinking of a design that can handle both scenarios reasonably well, lets call it <em>progressive design enhancement</em>.</p>
<p><img src="/img/before-hydration.png" alt="A code annotation component before hydration" normalWidth="2218" normalHeight="1698" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAACXBIWXMAABYlAAAWJQFJUiTwAAABD0lEQVR4nD2PTUrDcBBH/wfqEQq6ctVNF57BG3SvuBMKgnfowoWaRRUrgYK28SMhDU2DqZH0K7WxaVIbU4OUJ03VxfB+wzyGGZGEc3zHIXrzWQQBrqbTbTR5fVTp6RoDQydZLBDrYbm4y1GhyMHWDkqlwmzkMTJN7CeDS6nOd7pEBJOQ/XyBUi6fUTos0/cj3tOUeselfNEgXa0Qw+mc03uLY1njZK9ETgi2hUAJZrS7HqY7YTr/RETxks7LGN0e0jy/RqnWacoKbWfMc8+nZY9YO+IrjrFv73CUh+ypgWFk9/V1HVdVM6ZJshHVM4lW9QqzVkOTfvONnPUdWWbtiI8o+t/iWVbGTf1lizAM+QHBTgxxwwqTNgAAAABJRU5ErkJggg=="/></p>
<!-- -->
<p><img src="/img/after-hydration.png" alt="A code annotation component after hydration" normalWidth="2146" normalHeight="1671" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAACXBIWXMAABYlAAAWJQFJUiTwAAABGElEQVR4nCWPvUoDURCFb+FD+AD2Vj6Alb1gZ60oCLa2onZGCystFCxEO39Rq0TWrHpdCUkuIrvZzbJx45ofouJC5JJP9mZgmMPMOWdmhNaatu/Ti2N+Wh2DA/lMVCoTK0XzVdFtNBADrblYX+NwcYmDuQVuc1sEUhpSrBSF8wIftQCRpn9sTs0wPzLG8ug4O9OzeI4i6fdJflNWjvJUok9E9zvlquKzayv2946ZFIIJIchLj1I9wXvvmBSp1lSDxDTvTi45W81xurGNLd9QUQvHjXGjNsIrFnGtorknfCkROg6Nctk84z888ZUkZCF6cdM0Xcsyg0xYvb6hZtsGZ6KMLPRgQCcMca37oavj4D9KUzODuhxu+AfEcwu7nRpMogAAAABJRU5ErkJggg=="/></p>
<p>As a general rule, when JavaScript is not available always try to show something, and preferably something that takes up the same amount of vertical space so as not to cause a layout shift.</p>
<h2>How to detect if JavaScript is loaded/enabled?</h2>
<p>When Next.js performs SSG it does not execute any effect hooks in your code.
If a <code>useEffect</code> hook executed at least once then this means JavaScript must be loaded. We can add a CSS class to the root node to indicate this.</p>
<pre lang="jsx" messages="[object Object]"><code class="language-ts">// _app.jsx
function MyApp({ Component, pageProps, router }) {
  const [mounted, setMounted] = useState(false)
  useEffect(() =&gt; setMounted(true), [])
  return (
    &lt;Component className={mounted ? &quot;has-js&quot; : &quot;no-js&quot;} {...pageProps} /&gt;
  )
}
</code></pre>
<p>From anywhere in our App we can show and hide components easily depending on if there is JavaScript.</p>
<pre lang="jsx"><code class="language-ts">export const ShowWhenJSLoaded = styled.span`
  display: none;
  .has-js &amp; {
    display: contents;
  }
`
export const ShowBeforeJSLoaded = styled.span`
  display: none;
  .no-js &amp; {
    display: contents;
  }
`

const MyComp = () =&gt; (
  &lt;&gt;
    &lt;ShowBeforeJSLoaded&gt;
      &lt;Placeholder /&gt;
    &lt;/ShowBeforeJSLoaded&gt;
    &lt;ShowWhenJSLoaded&gt;
      &lt;SomeComponentThatNeedsJS /&gt;
    &lt;/ShowWhenJSLoaded&gt;
  &lt;/&gt;
)
</code></pre>
<h2>How to test your site works without JavaScript?</h2>
<p><img src="/img/js-disabled.png" alt="Disabling JavaScript with Chrome dev tools." normalWidth="2197" normalHeight="1605" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAHCAYAAAAxrNxjAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA+UlEQVR4nEWOsUrDcByEf4sP4eKkzu3iAwgiujgUJxHE4iM4CC5x0F0sOAiddBBLoIga/lZDqrSkbTSaRB0kk0MQJFEXG+GTRKoHB/dxw50YhsJQippep3ndptNzc2f5stnCtiw292qIpmmYpkmSJGSKougvD3TW8ZABhGFIpbLDfKlEeXkJu9fjLX7PO+U+IoZq0GrbHBzprG9scapM6icNri5Mnnwfx3HQdg+RWy/gNXphYW0bGSoiI5PI+AwyOkFZW+UzjqkeW//T2fnqvs5d8MxHv8/UWIHF6dm8O795QNI0zSEIPPx7l+/0K+eCDLNSnPv92PX5ATmh3vSf9SGXAAAAAElFTkSuQmCC"/></p>
<p>To manually disable JavaScript in the developer tools type <code>Ctrl+Shift+p</code> to open the command pallet and then search for <code>disable JavaScript</code>. If you now reload the page you will be seeing what non JavaScript users and search engine crawlers see.</p>
<p>If you use Styled Components, its worth checking that all your links function at this point. When using the <code>next/link</code> component around a styled component you will find that the <code>passHref</code> must be passed for the links to work without JavaScript. This is important for SEO because search engine crawlers use links on your site to discover its pages.</p>
<pre lang="jsx" style="--message-margin:0rem" messages="[object Object]"><code class="language-ts">// Example
export const Comp = () =&gt; {
  return (
    &lt;Link passHref href={`/index`}&gt;
      &lt;MyStyledComponent&gt;Click Me!&lt;/MyStyledComponent&gt;
    &lt;/Link&gt;
  )
}
const MyStyledComponent = styled.a`
  padding: 20px;
`
</code></pre>
<h2>Help! SSG Isn&#x27;t working!</h2>
<p>Be aware that when using <code>yarn dev</code> you are not seeing the finished product.
This is because SSR/SSG is not done in development mode.
To be able to test your app locally you need to run <code>yarn build &amp;&amp; yarn start</code> which will generate the production code.
Unlike other workflows, with Next.js and any other SSR/SSG supporting framework, production code does not just mean &quot;optimised&quot; or &quot;minified&quot;, the way your code is delivered and performs will be fundamentally different. In particular, hydration related bugs will only be apparent in the production build.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <category label="frontend"/>
        <category label="nextjs"/>
        <category label="react"/>
        <category label="ssg"/>
        <category label="ssr"/>
        <category label="styled-components"/>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why your auth provider isn't working in Cypress]]></title>
        <id>https://www.tomoliver.net/posts/cypress-samesite-problem</id>
        <link href="https://www.tomoliver.net/posts/cypress-samesite-problem"/>
        <updated>2024-05-08T14:56:18.364Z</updated>
        <summary type="html"><![CDATA[Auth provider not working in Cypress? Cookies not being set properly inside the Cypress test runner? In this guide we explain the cause and solution.]]></summary>
        <content type="html"><![CDATA[<p><img src="/img/samesite.png" alt="SameSite error in Chrome devtools" normalWidth="1850" normalHeight="578" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAADCAYAAACqPZ51AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAZUlEQVR4nGNYOH/2/03rV///+O7Vf3yAIaiw4z8I53bN/d++eNP/9oUb/jfMXv2/ee7a/4UTF/3P7J4LxgxO0cX/w4u6/ue2z/7fOGvV/9qpy8G4YuLi/xyadv8Z2BT+y9uG/QcA/JNV0c57l/YAAAAASUVORK5CYII=" smallSrc="/img/samesite-small.png" smallWidth="746" smallHeight="248"/></p>
<p>The web application that I am currently working on uses several 3rd party auth providers as part of its authentication flow. Manually testing the application I could see that everything was working. However, when running Cypress tests I was getting a strange warning mark on the header of the response from my auth provider. This header was asking my browser to set the session cookie but it was refusing to do so.</p>
<p>Naughty browser.</p>
<h2>What is the SameSite attribute?</h2>
<p>The <code>SameSite</code> attribute is basically a measure taken to mitigate CSRF attacks. It tells the browser how it should treat cookies across multiple domains.
The <code>SameSite</code> attribute can have one of three values:</p>
<ol>
<li><code>None</code>
<ul>
<li>The browser will allow all cross-site cookies.</li>
</ul>
</li>
<li><code>Lax</code>
<ul>
<li>The browser will allow cross-site cookies if the target browsing context is &quot;top-level&quot;.<!-- -->
<ul>
<li>What is a &quot;top-level&quot; browsing context?<br/>
<!-- -->A browsing context is the environment in which a browser displays a document. In modern browsers, it usually is a tab, but can be just a part of a page, like an <code>iframe</code>. Top-level simply means there is no parent context. For example, a tab is a top-level browsing context whereas an <code>iframe</code> exists within a tab (as a child browsing-context) and so cannot be considered top-level.</li>
</ul>
</li>
</ul>
</li>
<li><code>Strict</code>
<ul>
<li>The browser does not allow any cross-site cookies.</li>
</ul>
</li>
</ol>
<p><strong>Note</strong>: Should the <code>SameSite</code> attribute header be omitted, a default of <code>Lax</code> will be applied by most browsers.</p>
<h2>Why does it work outside of Cypress?</h2>
<p>During the test, redirecting to the auth provider does not change the address in the URL bar of the Cypress test runner. This is because the entire application being tested, including any external redirects, is contained within an <code>iframe</code>. This does not constitute a top-level browsing context and so violates the <code>SameSite=Lax</code> restrictions. Outside of Cypress there is no <code>iframe</code> and so the redirect takes place without breaking the rules.</p>
<h2>Solution</h2>
<p>Luckily Cypress provides the <code>cy.intercept</code> function that will allow us to intercept and modify all responses including redirects. We can use it to rewrite the headers of any response to: <code>SameSite=None</code>, thus allowing the browser to set our cookies.</p>
<p>We can abstractify this to a Cypress command so we can call it from any test.</p>
<pre lang="ts" style="--message-margin:0" messages="[object Object],[object Object]"><code class="language-ts">
// commands.ts
Cypress.Commands.add(&quot;rewriteHeaders&quot;, () =&gt; {
  cy.intercept(&quot;*&quot;, (req) =&gt;
    req.on(&quot;response&quot;, (res) =&gt; {
      const setCookies = res.headers[&quot;set-cookie&quot;]
      res.headers[&quot;set-cookie&quot;] = (
        Array.isArray(setCookies) ? setCookies : [setCookies]
      )
        .filter((x) =&gt; x)
        .map((headerContent) =&gt;
          headerContent.replace(
            /samesite=(lax|strict)/gi,
            &quot;secure; samesite=none&quot;
          )
        )
    })
  )
})



</code></pre>
<p>We can now use our custom command before each test run begins.</p>
<pre lang="ts"><code class="language-ts">// mytest.cy.ts
describe(&quot;Logs in&quot;, () =&gt; {
  beforeEach(() =&gt; {
    cy.rewriteHeaders()
  })
  it(&quot;should log in without errors&quot;, () =&gt; {
    cy.contains(&quot;LOGIN&quot;).click()
    ...
</code></pre>
<p>And thats all! Hopefully this saves someone a few hours of debugging!</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <category label="nextjs"/>
        <category label="react"/>
        <category label="cypress"/>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[How to use your DSLR from 2008 as a webcam in 2022 (NixOS)]]></title>
        <id>https://www.tomoliver.net/posts/using-an-slr-as-a-webcam-nixos</id>
        <link href="https://www.tomoliver.net/posts/using-an-slr-as-a-webcam-nixos"/>
        <updated>2024-05-08T14:56:18.374Z</updated>
        <summary type="html"><![CDATA[Why throw away your old Camera? In this guide we'll show you how simple it is to reuse your old Camera as a webcam using the NixOS linux distribution.]]></summary>
        <content type="html"><![CDATA[<p><img src="/img/webcam.jpg" alt="Canon eos rebel xs being used as a webcam" normalWidth="2068" normalHeight="1914" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAJCAIAAACExCpEAAAACXBIWXMAAAsTAAALEwEAmpwYAAABIklEQVR4nAEXAej+AJpSUP0AAP0AAP5iK+2Ga4RzdbJIQv0AK+jk5LK0tAC2ubP+NED9AACEXVtOaX5HRUVCQDzWpJz////W1tYAu7+n6N/S/op2fUlNO0BIQ0NJPjI0opya/f3/2NjUAFRUVH6IineToWZ1hFZlcV5eXmJgYHZ5gpOTl397eABcXmPs7O709Pj4+v77/f/9/f3+/v76/Pz///+5ub0AS01U4+Pj+vr6/v7++fn59ff5/P7+/v7+////pKSmAC8yNcrKyu/v7/f3+fL09vr4+vz8/Pb29unp6YGBgwAIBwqMjIzQ0M7d3d/c3uLg4uba3N/b29u/v79HR0kAAAAAFhYWbGxsn5+hqqqusbGzrKyuk5OTSEhIAQEBA62sFaLDtEMAAAAASUVORK5CYII=" smallSrc="/img/webcam-small.jpg" smallWidth="1364" smallHeight="1144"/></p>
<p>This year after largely abandoning my macbook in favour of a nixos machine, I started getting requests to &quot;turn my camera on&quot; when video calling people. This was a problem because I didn&#x27;t actually have a webcam. I thought about buying one but then I realised that I had a perfectly good Canon EOS rebel XS DSLR circa 2008 lying around on my shelf. This camera has a mini-USB port, so naturally I pondered: DSLR + mini-USB + desktop PC = possible webcam ?</p>
<p>But there is just one problem. My Canon EOS rebel XS isn&#x27;t actually capable of recording video. It can take some nice pictures but that&#x27;s about it. So that&#x27;s the end of that then.</p>
<p><em>Or is it?</em></p>
<p>There happens to be some amazing open source software called gphoto2.
Once installed it will allow you to control an array of different supported cameras from your computer (find out if yours is supported with <code>gphoto2 --list-cameras</code>). This includes taking photos and videos.
After installing, try taking a picture with it like so:
<code>gphoto2 --capture-image-and-download</code>. You should hear the shutter activate and the image will be saved to your current working directory.</p>
<p>Despite the aforementioned lack of video functionality on my camera, I decided to try <code>gphoto2 --capture-movie</code> anyway. Somehow, although my camera does not support video natively, this tool still manages to spit out an mjpeg file (For my camera I needed to put it in &quot;live-view&quot; mode before gphoto2 could record video. This consisted of putting it on portrait mode and then pressing the &quot;set&quot; button so that the viewfinder is off and the screen is displaying an image). Unfortunately though this is not enough to be able to use it as a webcam. It still needs to get assigned a video device such as <code>/dev/video**</code>.</p>
<h2>How do?</h2>
<p>First of all if you haven&#x27;t already, you&#x27;re gonna want to grab <code>gphoto2</code> and <code>ffmpeg</code>.</p>
<p>And maybe <code>mpv</code> also.</p>
<pre lang="nix"><code class="language-nix"># configuration.nix
...
environment.systemPackages = with pkgs; [
  ffmpeg
  gphoto2
  mpv
...
</code></pre>
<p>To create the virtual video device we will need to make use of the v4l2 Linux kernel module. It can be installed by adding to the extra module packages in configuration.nix.</p>
<pre lang="nix"><code class="language-nix"># configuration.nix
...
boot.extraModulePackages = with config.boot.kernelPackages;
[ v4l2loopback.out ];
boot.kernelModules = [
  &quot;v4l2loopback&quot;
];
boot.extraModprobeConfig = &#x27;&#x27;
  options v4l2loopback exclusive_caps=1 card_label=&quot;Virtual Camera&quot;
&#x27;&#x27;;
...
</code></pre>
<p>You will now need to run <code>sudo nixos-rebuild switch</code> and also reboot your computer since we have made some changes to the kernel.</p>
<p>Now try running this command:</p>
<pre lang="shell"><code class="language-shell">   gphoto2 --stdout --capture-movie |
    ffmpeg -i - -vcodec rawvideo -pix_fmt yuv420p -f v4l2 /dev/video0
</code></pre>
<p>You should see output like this:</p>
<pre><code>ffmpeg version 4.4.1 Copyright (c) 2000-2021 the FFmpeg developers
  built with gcc 11.3.0 (GCC)
  configuration: --disable-static ...
  libavutil      56. 70.100 / 56. 70.100
  libavcodec     58.134.100 / 58.134.100
  libavformat    58. 76.100 / 58. 76.100
  libavdevice    58. 13.100 / 58. 13.100
  libavfilter     7.110.100 /  7.110.100
  libavresample   4.  0.  0 /  4.  0.  0
  libswscale      5.  9.100 /  5.  9.100
  libswresample   3.  9.100 /  3.  9.100
  libpostproc    55.  9.100 / 55.  9.100
Capturing preview frames as movie to &#x27;stdout&#x27;. Press Ctrl-C to abort.
[mjpeg @ 0x1dd0380] Format mjpeg detected only with low score of 25, misdetection possible!
Input #0, mjpeg, from &#x27;pipe:&#x27;:
  Duration: N/A, bitrate: N/A
  Stream #0:0: Video: mjpeg (Baseline), yuvj422p(pc, bt470bg/unknown/unknown), 768x512 ...
Stream mapping:
  Stream #0:0 -&gt; #0:0 (mjpeg (native) -&gt; rawvideo (native))
[swscaler @ 0x1e27340] deprecated pixel format used, make sure you did set range correctly
Output #0, video4linux2,v4l2, to &#x27;/dev/video0&#x27;:
  Metadata:
    encoder         : Lavf58.76.100
  Stream #0:0: Video: rawvideo (I420 / 0x30323449) ...
    Metadata:
      encoder         : Lavc58.134.100 rawvideo
frame=  289 fps= 23 q=-0.0 size=N/A time=00:00:11.56 bitrate=N/A speed=0.907x
</code></pre>
<p>Now try this command:</p>
<pre lang="shell"><code class="language-shell">mpv av://v4l2:/dev/video0 --profile=low-latency --untimed
</code></pre>
<p>You should now be able to see the video feed from your webcam.</p>
<p><img src="/img/streaming-webcam.png" alt="Streaming a live feed from the webcam" normalWidth="2200" normalHeight="1650" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAIAAABPmPnhAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAwklEQVR4nF3JvQ7BUACG4a/tgIHBUmm16c/p0CAkFjcgWBiNrsCVuAgkHUqLyYWpnHPUkZYSA+/4PjgdTmEYB5vNLgj2YXiM4+O3cxQhpfRxv2ePLM+y/DchBIQQf/eHL5crYzfG2JXylFORUs4pY4xzniQJDMfTTDLvVWadWtNydcNuurbp2qblGI4HyDKAroyWgrGK1RD9Eqp4V5CkALAVaMB6gnyLhVuwJH240Ybq13ViGWQ0IMup7nukrBGo/oueN3t5f12GElsAAAAASUVORK5CYII="/></p>
<h2>What about starting the webcam automatically?</h2>
<p>It is a bit annoying to have to execute a command every time we want to use our webcam. Luckily there are ways for this command to be automatically run on startup.</p>
<p>I have decided to implement this webcam startup command as a systemd service.</p>
<pre lang="nix" messages="[object Object]"><code class="language-nix"># configuration.nix
...
  systemd.services.webcam = {
    enable = true;
    script = &#x27;&#x27;
      ${pkgs.gphoto2}/bin/gphoto2 --stdout --capture-movie |
        ${pkgs.ffmpeg}/bin/ffmpeg -i - \
            -vcodec rawvideo -pix_fmt yuv420p -f v4l2  /dev/video0
    &#x27;&#x27;;
wantedBy = [ &quot;multi-user.target&quot; ];
  };
...
</code></pre>
<p>Now if you do a quick <code>sudo nixos-rebuild switch</code> and reboot your computer you should find that the webcam service is running.</p>
<p>To check for any problems we can use <code>systemctl status webcam</code> which will tell us the last time the service was run as well as log of its last output. Handy for debugging.</p>
<h2>Are we done?</h2>
<p>Its very tempting to stop here.
However considering the current global crises it may be pertinent to wonder whether it is necessary to have the webcam on all the time. It strikes me as sub-optimal for 3 obvious reasons:</p>
<ol>
<li>Its a waste of electricity.</li>
<li>There are privacy concerns associated with this kind of thing.</li>
<li>I have to keep changing the battery (solved by <a href="https://www.amazon.co.uk/F1TP-Adapter-Battery-Coupler-Cameras/dp/B096KQWVTL">this</a>)</li>
</ol>
<p>My camera has a lens cap, so to be honest the second point does not really bother me. I can always put the lens cap on when I am not using the webcam to make sure certain government agencies aren&#x27;t being entertained at my expense.
However, leaving a big power hungry DSLR camera on 24/7 certainly is not doing anything for my electricity bill. That&#x27;s not to mention the CPU overhead required for decoding the video... which upon measurement with <code>htop</code> looks to be around 10%. Not insignificant especially when considering that I&#x27;m not exactly running a podcast here, I don&#x27;t have that many video calls in a day.</p>
<p>The ideal scenario:</p>
<ul>
<li>I leave my camera plugged in to my computer all the time but switched <em>off</em></li>
<li>When I want to use the webcam I switch on the camera with its power button</li>
<li>My computer then automatically detects the camera and starts the systemd service</li>
<li>After finishing with the webcam I switch it off again</li>
</ul>
<p>To achieve this we need to make use of a custom udev rule.
A udev rule is something that tells your computer to perform a certain task when it discovers that a device has become available. This could be an external hard drive or even non-usb devices.
In our case we need it to recognise the camera through its usb connection.</p>
<p>We need to specify what command is to be run when the udev rule is triggered.
For that I am creating a derivation (nix package) that simply restarts the systemd service. You could also add logging to this for debugging purposes.</p>
<pre lang="nix" messages="[object Object]"><code class="language-nix"># start-webcam.nix
with import &lt;nixpkgs&gt; { };

writeShellScriptBin &quot;start-webcam&quot; &#x27;&#x27;
  systemctl restart webcam
  # debugging example
  # echo &quot;hello&quot; &amp;&gt; /home/tom/myfile.txt
  # If myfile.txt gets created then we know the udev rule has triggered properly
&#x27;&#x27;
</code></pre>
<p>Now to actually define the udev rule.
First of all we need to find out the device and vendor id of the camera.
This is done using the <code>lsusb</code> command. Since I don&#x27;t see myself using this particularly often I will install it temporarily using <code>nix-shell</code>.</p>
<p>This can be done like so: <code>nix-shell -p usbutils</code></p>
<p>Then running <code>lsusb</code> we get the following output:</p>
<pre lang="shell"><code class="language-shell">[nix-shell:~/environment]$ lsusb
...
Bus 002 Device 008: ID 04a9:317b Canon, Inc. Canon Digital Camera
...
</code></pre>
<p>We can see from this output that the vendor ID is <code>04a9</code> and the device ID is <code>317b</code>.</p>
<p>We can now create the udev rule.</p>
<pre lang="nix" messages="[object Object]"><code class="language-nix"># configuration.nix
...
let
  startWebcam = import ./start-webcam.nix;
...
services.udev.extraRules = &#x27;&#x27;
  ACTION==&quot;add&quot;,  \
  SUBSYSTEM==&quot;usb&quot;, \
  ATTR{idVendor}==&quot;04a9&quot;, \
  ATTR{idProduct}==&quot;317b&quot;,  \
  RUN+=&quot;${startWebcam}/bin/start-webcam&quot;
&#x27;&#x27;;
...
</code></pre>
<p>We just need to remove the <code>wantedBy = [ &quot;multi-user.target&quot; ];</code> line in our systemd service. If we leave this in then the service will start automatically when we next reboot whether the camera is switched on or not.</p>
<p>One more <code>sudo nixos-rebuild switch</code> and we are finished!</p>
<p>Thanks for reading this far.
I hope this article has made you think twice before chucking some of your old tech.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <category label="nixos"/>
        <category label="hardware"/>
        <category label="linux"/>
        <category label="lifestyle"/>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Are you committing from Vim the fast way?]]></title>
        <id>https://www.tomoliver.net/posts/small-commit-shortcut-for-vim</id>
        <link href="https://www.tomoliver.net/posts/small-commit-shortcut-for-vim"/>
        <updated>2024-05-08T14:56:18.373Z</updated>
        <summary type="html"><![CDATA[Tired of suspending Vim to commit from the command line? Why not generate your commit command from within Vim using this simple shortcut.]]></summary>
        <content type="html"><![CDATA[<p>Sometimes I just change 1 or 2 lines and want to commit &amp; push straight away.
Its times like these where I don&#x27;t really want to have to check the diff and stage each file etc...
But I still want to enforce some kind of commit message convention, namely:</p>
<pre><code class="language-txt">&lt;BRANCH_NAME&gt; &lt;COMMIT_TYPE&gt; &lt;MESSAGE&gt;
e.g. main fix: made background white
</code></pre>
<p>So  <code>&lt;COMMIT_TYPE&gt;</code> is <code>fix</code> in this case.<br/>
<!-- -->Because I use vim, I want to introduce a shortcut into my workflow in order to make this process easier.
So my goal is to press a key combination in vim and the below command to populate the command input box:</p>
<pre lang="vim"><code class="language-js">:G commit -am &quot;&lt;BRANCH_NAME&gt; fix: &lt;cursor here&gt;&quot;
</code></pre>
<p>It should show up like this:</p>
<p><img src="/img/smol-commit.png" alt="Git commit command generated in vim command bar" normalWidth="730" normalHeight="426" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAqUlEQVR4nGNgYGD4L8LP/5+Diem/oIDAfzk5uf9KSkr/VVRUwGwQVlBQ+M9w8ezJ/59ePPv/+Pbt/39+/vr/7+/f/9gAw8u3H/9/+fbl/+fPn///+PHj/9fv38FsEP7w4QMYg9gM//7+w2oCholtl6b+77s+7f+K3Sv+r1+1/v+UKVOwYgaGheb/GWbq/48pj/6fFBX/38rM7L+NldV/c2NjMNtIVxfMBgCB0q9sUD0BGwAAAABJRU5ErkJggg==" smallSrc="/img/smol-commit-small.png" smallWidth="864" smallHeight="107"/></p>
<p>First of all lets decide what the keybinding will be.<br/>
<!-- -->I&#x27;m a simple man, I&#x27;m gonna bind it to <code>,f</code> so I remember it.<br/>
<!-- -->(because its <code>f</code> for <code>fix</code> XD)</p>
<pre lang="vim"><code class="language-js">noremap ,f  ...
</code></pre>
<p>Next we need to create the git command.
I am using <code>:G</code> that comes from the <a href="https://github.com/tpope/vim-fugitive">vim-fugitive</a> plugin by tpope. Its basically just a shortcut for <code>:!git</code> so feel free to do that instead.</p>
<p>Next is <code>commit -am</code>. Most people know about <code>-m</code> but fewer have heard of <code>-am</code>.<br/>
<!-- -->It basically is the same as <code>-m</code> but it automatically stages all the changes you have in the repo.</p>
<pre lang="vim"><code class="language-vim">noremap ,f :G commit -am &quot;&quot;
</code></pre>
<p>Now we just need to generate the commit message, and for that we need to know what the name of the current branch is. Usually we can do that with <code>git branch --show-current</code>
however, since git is external to vim we need some special syntax.</p>
<p>To execute an external command we can use the <code>system</code> function like so:</p>
<pre lang="vim"><code class="language-vim">...&quot;&lt;C-R&gt;=system(&quot;git branch --show-current&quot;)&lt;CR&gt;
</code></pre>
<p>For some reason <code>^@</code> gets stuck to the end of whatever the branch name is.
To get rid of that we can backspace <code>&lt;BS&gt;</code> once.
Now we press the <code>&lt;Left&gt;</code> key to move the cursor one space left inside the quotes.</p>
<pre lang="vim"><code class="language-js">...&quot;&lt;C-R&gt;=system(&quot;git branch --show-current&quot;)&lt;CR&gt;&lt;BS&gt;&quot;&lt;Left&gt;
</code></pre>
<p>Now leave a <code>&lt;Space&gt;</code> and write the commit type, in this case <code>fix</code>.<br/>
<!-- -->Finally add a colon and another <code>&lt;Space&gt;</code> for the commit message and we are done.</p>
<pre lang="vim"><code class="language-js">...&quot;&lt;C-R&gt;=system(&quot;git branch --show-current&quot;)&lt;CR&gt;&lt;BS&gt;&quot;&lt;Left&gt;&lt;Space&gt;fix:&lt;Space&gt;
</code></pre>
<p>Putting it altogether we get the finished product:</p>
<pre lang="vim"><code class="language-js">noremap ,f :G commit -am &quot;&lt;C-R&gt;=system(&quot;git branch --show-current&quot;)&lt;CR&gt;&lt;BS&gt;&quot;&lt;Left&gt;&lt;Space&gt;fix:&lt;Space&gt;
</code></pre>
<p>Now you too can make a smol commit from vim with just a few keystrokes!</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <category label="vim"/>
        <category label="linux"/>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Typing a classic JS one-liner]]></title>
        <id>https://www.tomoliver.net/posts/counting-occurrences</id>
        <link href="https://www.tomoliver.net/posts/counting-occurrences"/>
        <updated>2024-05-08T14:56:18.362Z</updated>
        <summary type="html"><![CDATA[Still writing Javascript using loops and if statements? In this post we show how imperative code can be refactored into beautiful functional perfection.]]></summary>
        <content type="html"><![CDATA[<p>Often we have a list of things and would like to know how many there are of each thing in the list.
This is one of those handy one-liners that will give you an object mapping an item to the number of occurrences for a given list of things.</p>
<pre lang="js"><code class="language-js">const getFreqMap = (list) =&gt; 
  list.reduce((acc, cur) =&gt; 
    ({ ...acc, [cur]: (acc[cur] ?? 0) + 1 }), {})

getFreqMap([&#x27;a&#x27;, &#x27;a&#x27;, &#x27;c&#x27;, &#x27;b&#x27;, &#x27;d&#x27;, &#x27;d&#x27;, &#x27;d&#x27;])   
// {a: 2, c: 1, b: 1, d: 3}
getFreqMap([0, 1, 2, 2, 6, 2, 1, 0, 6])           
// {0: 2, 1: 2, 2: 3, 6: 2}
</code></pre>
<p>Okay so maybe its not just one line.<br/>
<!-- -->But that&#x27;s because I formatted it nicely!<br/>
<!-- -->At least its not as bad as this:</p>
<pre lang="js"><code class="language-js">const getFreqMap = (list) =&gt; {
  const res = {}
  for (let i = 0; i &lt; list.length; i++) {
    if (res[list[i]] !== undefined){
      res[list[i]]++
    }
    else{
      res[list[i]] = 1
    }
  }
  return res
}
</code></pre>
<p>Wow, what a waste of lines...<br/>
<!-- -->Anyway, lets rewrite our one-liner in typescript because its better.</p>
<br/>
<!-- -->
<p>So lets just change the file type to <code>.ts</code>.<br/>
<!-- -->...And immediately we get a bollocking like so:</p>
<pre lang="ts" messages="[object Object],[object Object],[object Object]"><code class="language-ts">const getFreqMap = (list) =&gt;
  list.reduce((acc,cur) =&gt; 
    ({ ...acc, [cur]: (acc[cur] ?? 0) + 1 }), {})
</code></pre>
<p>So lets add a type annotation.</p>
<pre lang="ts" messages="[object Object],[object Object]"><code class="language-ts">const getFreqMap = (list: string[]) =&gt;
  list.reduce((acc,cur) =&gt; 
    ({ ...acc, [cur]: (acc[cur] ?? 0) + 1 }), {})
</code></pre>
<p>Looks like we need to tell typescript that the type of the second argument of reduce is a an object mapping type <code>string</code> to type <code>number</code>.<br/>
<!-- -->We can do this using <code>as</code> .</p>
<pre lang="ts" messages="[object Object]"><code class="language-ts">const getFreqMap = (list: string[]) =&gt;
  list.reduce(
    (acc, cur) =&gt; ({ ...acc, [cur]: (acc[cur] ?? 0) + 1 }),
    {} as { [key: string]: number }
  )
</code></pre>
<p>And this works just fine.<br/>
<!-- -->But.. Wouldn&#x27;t it be nice if it worked for any kind of list?<br/>
<!-- -->So lets do that then...<br/>
<!-- -->Using generics of course.</p>
<pre lang="ts" messages="[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]"><code class="language-ts">const getFreqMap = &lt;A&gt;(list: A[]) =&gt;
  list.reduce(
    (acc, cur) =&gt; ({ ...acc, [cur]: (acc[cur] ?? 0) + 1 }),
    {} as { [key: A]: number }
  )
</code></pre>
<p>So we just replaced <code>string</code> with a generic type <code>A</code>.<br/>
<!-- -->But alas this is not correct...<br/>
<!-- -->We get an error saying we can&#x27;t use something with type <code>A</code> as an index on <code>{}</code>.<br/>
<!-- -->So lets see about the <code>Record</code> type instead.</p>
<pre lang="ts" messages="[object Object],[object Object]"><code class="language-ts">const getFreqMap = &lt;A&gt;(list: A[]) =&gt;
  list.reduce(
    (acc, cur) =&gt; ({ ...acc, [cur]: (acc[cur] ?? 0) + 1 }),
    {} as Record&lt;A, number&gt;
  )
</code></pre>
<p>Omfg another error, srsly???</p>
<p>Oh. Looks like our generic type is a little too generic..<br/>
<!-- -->Lets take a look at the type definition for the Record type.</p>
<pre lang="ts"><code class="language-ts">type Record&lt;K extends keyof any, T&gt; = {
  [P in K]: T;
};
</code></pre>
<p>It says that the type of <code>K</code> (the key of the Record) must be a key of something.<br/>
<!-- -->Which gets reduced to <code>string</code>, <code>number</code>, <code>symbol</code> or <code>any</code> as stated by the error message above.<br/>
<!-- -->Ok. So lets just add that type constraint to <code>A</code>.</p>
<pre lang="ts" style="--message-margin:0rem" messages="[object Object]"><code class="language-ts">const getFreqMap = &lt;A extends keyof any&gt;(list: A[]) =&gt;
  list.reduce(
    (acc, cur) =&gt; ({ ...acc, [cur]: (acc[cur] ?? 0) + 1 }),
    {} as Record&lt;A, number&gt;
  )
</code></pre>
<p>And here is the finished product!<br/>
<!-- -->How beautiful!</p>
<p>But wait, what if we had a list of something more complicated, like a list of objects?<br/>
<!-- -->An exercise for the reader perhaps...</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <category label="typescript"/>
        <category label="functional-programming"/>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[How often is your mind worrying about problems, reliving unp...]]></title>
        <id>https://www.tomoliver.net/notes/2025-06-12-1726</id>
        <link href="https://www.tomoliver.net/notes/2025-06-12-1726"/>
        <updated>2025-07-22T15:16:03.755Z</updated>
        <summary type="html"><![CDATA[How often is your mind worrying about problems, reliving unpleasant experiences that continue to haunt you, or imagining a potential future that may never happen?]]></summary>
        <content type="html"><![CDATA[<p>How often is your mind worrying about problems, reliving unpleasant experiences that continue to haunt you, or imagining a potential future that may never happen?</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Anything that can happen can only happen now.
So if...]]></title>
        <id>https://www.tomoliver.net/notes/2025-05-27-1157</id>
        <link href="https://www.tomoliver.net/notes/2025-05-27-1157"/>
        <updated>2025-06-06T15:18:15.187Z</updated>
        <summary type="html"><![CDATA[Anything that can happen can only happen now.
So if you want some change, now is the place to be.]]></summary>
        <content type="html"><![CDATA[<p>Anything that <em>can</em> happen can <em>only</em> happen <em>now</em>.<br/>
<!-- -->So if you want some change, now is the place to be.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Maybe the ego is like the one ring in Lord Of The Rings.
S...]]></title>
        <id>https://www.tomoliver.net/notes/2025-05-13-1052</id>
        <link href="https://www.tomoliver.net/notes/2025-05-13-1052"/>
        <updated>2025-06-07T12:08:26.483Z</updated>
        <summary type="html"><![CDATA[Maybe the ego is like the one ring in Lord Of The Rings.
Some of the characters believe that they can harness its power for good.
But the wise characters know it will consume whoever tries.
"Do Not Tempt Me, Frodo!" - Gandalf The Grey]]></summary>
        <content type="html"><![CDATA[<p>Maybe the ego is like the one ring in <em>Lord Of The Rings</em>.<br/>
<!-- -->Some of the characters believe that they can harness its power for good.<br/>
<!-- -->But the wise characters know it will consume whoever tries.<br/>
<em>&quot;Do Not Tempt Me, Frodo!&quot; - Gandalf The Grey</em></p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[This is why I unconsciously compare myself with others.
...]]></title>
        <id>https://www.tomoliver.net/notes/2025-05-04-1751</id>
        <link href="https://www.tomoliver.net/notes/2025-05-04-1751"/>
        <updated>2025-06-07T12:08:26.483Z</updated>
        <summary type="html"><![CDATA[This is why I unconsciously compare myself with others.
There is a part of me that is afraid that I don't deserve to exist.
So it wants to find evidence to justify it's existence. 
If it can find someone it perceives as inferior to me, ...]]></summary>
        <content type="html"><![CDATA[<p><strong>This is why I unconsciously compare myself with others.</strong></p>
<p>There is a part of me that is afraid that I don&#x27;t deserve to exist.</p>
<p>So it wants to find evidence to justify it&#x27;s existence.</p>
<p>If it can find someone it perceives as inferior to me, then that can temporarily placate its fear.</p>
<p>&quot;At least I am better than that guy! Phew...&quot;</p>
<p>But eventually it always finds someone who it perceives as superior.</p>
<p>That really gets it going.</p>
<p>Now it needs to think of ways that I am <em>actually</em> somehow superior.</p>
<p>Maybe that person is secretly unhappy? Haha, I bet I am way happier than them!</p>
<p>But what if they are actually genuinely happy?</p>
<p>Well then, the only reason is that there is some unfair advantage that they possess over me. Its not fair. I have been poorly treated and so was not given a fair chance to compete...</p>
<p>I am a victim.</p>
<p>...</p>
<p>And on and on it goes...
It is very interesting. Why can&#x27;t it accept inferiority?
<br/>
<br/>
<!-- -->I think its because, according to it, to accept inferiority is to accept annihilation.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Books I've read so far in 2025. WIP
Books I'd read again:

...]]></title>
        <id>https://www.tomoliver.net/notes/2025-04-18-0845</id>
        <link href="https://www.tomoliver.net/notes/2025-04-18-0845"/>
        <updated>2025-06-07T12:08:26.482Z</updated>
        <summary type="html"><![CDATA[Books I've read so far in 2025. WIP
Books I'd read again:

Another Self
The Top Five Regrets of the Dying
嫌われる勇気
The divided mind
The way out
A new earth
The power of now
Stillness speaks
When the body says no
The myth of...]]></summary>
        <content type="html"><![CDATA[<p>Books I&#x27;ve read so far in 2025. WIP</p>
<p><br/>
<!-- -->Books I&#x27;d read again:</p>
<ul>
<li><a href="https://www.goodreads.com/book/show/206311440-another-self?from_search=true&amp;from_srp=true&amp;qid=iWOAp3owzT&amp;rank=5">Another Self</a></li>
<li><a href="https://www.goodreads.com/book/show/13059271-the-top-five-regrets-of-the-dying?ref=nav_sb_ss_1_12">The Top Five Regrets of the Dying</a></li>
<li><a href="https://www.goodreads.com/book/show/19480382?ref=nav_sb_ss_1_6">嫌われる勇気</a></li>
<li><a href="https://www.goodreads.com/book/show/130610.The_Divided_Mind?ac=1">The divided mind</a></li>
<li><a href="https://www.goodreads.com/book/show/50021854-the-way-out">The way out</a></li>
<li><a href="https://www.goodreads.com/book/show/76334.A_New_Earth?from_search=true&amp;from_srp=true&amp;qid=6UWx5x2x5U&amp;rank=1">A new earth</a></li>
<li><a href="https://www.goodreads.com/book/show/6708.The_Power_of_Now?from_search=true&amp;from_srp=true&amp;qid=KKggj4xcVs&amp;rank=1">The power of now</a></li>
<li><a href="https://www.goodreads.com/book/show/67864.Stillness_Speaks?from_search=true&amp;from_srp=true&amp;qid=Q0JT0Vtcf4&amp;rank=1">Stillness speaks</a></li>
<li><a href="https://www.goodreads.com/book/show/450534.When_the_Body_Says_No?from_search=true&amp;from_srp=true&amp;qid=PAB53lR0oW&amp;rank=1">When the body says no</a></li>
<li><a href="https://www.goodreads.com/book/show/58537332-the-myth-of-normal?ref=nav_sb_ss_1_14">The myth of normal</a></li>
<li><a href="https://www.goodreads.com/book/show/3601593-non-violent-communication-a-language-of-life?from_search=true&amp;from_srp=true&amp;qid=rDpaxx0p0p&amp;rank=1">Non violent communication</a></li>
<li><a href="https://www.goodreads.com/book/show/33534477-enlightenment-is-your-nature?ac=1&amp;from_search=true&amp;qid=v8JViONHt7&amp;rank=1">Enlightenment is you nature</a></li>
</ul>
<p><br/>
<!-- -->Books I probably wouldn&#x27;t:</p>
<ul>
<li><a href="https://www.goodreads.com/book/show/58665892-why-woo-woo-works?from_search=true&amp;from_srp=true&amp;qid=AzZ317srvD&amp;rank=1">Why woo-woo works</a></li>
<li><a href="https://www.goodreads.com/book/show/32191672-trauma-sensitive-mindfulness?ref=nav_sb_ss_2_17">Trauma-Sensitive Mindfulness</a></li>
<li><a href="https://www.goodreads.com/book/show/60726415-don-t-believe-everything-you-think">Don&#x27;t believe everything you think</a></li>
<li><a href="https://www.goodreads.com/book/show/34849164-sacred-rest?from_search=true&amp;from_srp=true&amp;qid=G5a7Ce3jKA&amp;rank=1">Sacred Rest</a></li>
</ul>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[アルジャーノンに花束を - Daniel Keyes 
アルジャーノンに花束を - Daniel Keyes
こ...]]></title>
        <id>https://www.tomoliver.net/notes/2024-07-08-0953</id>
        <link href="https://www.tomoliver.net/notes/2024-07-08-0953"/>
        <updated>2024-08-15T11:05:21.771Z</updated>
        <summary type="html"><![CDATA[アルジャーノンに花束を - Daniel Keyes 
アルジャーノンに花束を - Daniel Keyes
この本はなぜか日本で知られてるけどイギリスではそれほど有名じゃないようだ。
僕の大好きなNUJABESの「FEATHER」という曲に出てくるから知ってる： 

...
The best laid plans of Mice and Men are never right
I'm just a Vagabond with Flowers f...]]></summary>
        <content type="html"><![CDATA[<p><img src="/img/books/flowers-for-algernon.jpg" alt="アルジャーノンに花束を - Daniel Keyes " normalWidth="263" normalHeight="379" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAKCAIAAAD3rtNaAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA50lEQVR4nAHcACP/AImFdrnCtLu9tcLGuL/JxbnDwcHFuwC7r5X19dzj4cvg4cnk49Ps7trw6tAAuaqO/vXV7ubL8urQ8enO9OvT9OnLALWojP752P/33fnz2v744Pbw1O7oxwDHuJfu5cafm4vu5tCnpJHW0LT/+tMAin5lR0c/AAEGLSslBQgOKCgmr6GJAIN6ZU5MRxQoIwAeFBQyK0BCOrSrkwC8spTm4MI8PTlTWVFBQTvOyLD//NgAt6iI7OPEqqaU9+/Vsaub2NK8+vPSAJuTePfpyvHoyufexPHp0e3l0t/Yvsq/jf/6C8zmAAAAAElFTkSuQmCC"/></p>
<p><strong>アルジャーノンに花束を - Daniel Keyes</strong></p>
<p>この本はなぜか日本で知られてるけどイギリスではそれほど有名じゃないようだ。<br/>
<!-- -->僕の大好きなNUJABESの「FEATHER」という曲に出てくるから知ってる：</p>
<blockquote>
<p>...<br/>
<!-- -->The best laid plans of Mice and Men are never right<br/>
<!-- -->I&#x27;m just a Vagabond with Flowers for Algernon<br/>
<!-- -->An Average Joe who knows what the fuck is going on<br/>
<!-- -->...<br/>
<!-- -->読むしかない、、、
<br/>
<strong>＊＊ネタバレ注意＊＊</strong></p>
</blockquote>
<p>知的障害者でIQ８０の主人公が子供のように無邪気な生活を送ってる。<br/>
<!-- -->名前はチャーリーである。<br/>
<!-- -->チャーリーはベーカリーで働くこともでき、ある程度自立をしており、友達も少なくない。
ある日、チャーリーは科学者に選ばれ、「知能を向上させる手術ができるかもしれない」と告げられる。
チャーリーには、子供の頃から他の子供と同じように「利口になりたい」という夢があり、喜んで手術を受けることにした。
<br/>
<br/>
<!-- -->手術が成功し、チャーリーの知能は日々少しずつ向上していく。
知能が上がることで、これまで理解できなかった過去の記憶や経験を新たな視点で見直すようになる。彼が友達だと思っていた人々が実は彼を見下し、いじめていたことや、子供の頃に親から虐待を受けていたことに気づく。
チャーリーは知能を得ることで幸せになれると信じていたが、高い知能を持つことで人間の闇がはっきり見えるようになり、時には元の白痴状態がよかったのではないかと感じることもあった。
しかし、手術の影響で正常のIQに止まらず、時間が経てば経つほどチャーリーの知能が伸びる一方であった。
<br/>
<br/>
<!-- -->天才的なIQにまで達したチャーリーは、心理学者になり、手術を行った科学者たちが実験用に使用していたネズミ「アルジャーノン」を観察することで、恐ろしいことを予測する。
手術の効果は一時的なものであり、いずれ知能が元に戻る、むしろ、さらに低下してしまうという結論であった。
天才的なIQを持つ人間しか予測できなかったことを予測してしまい、能力があるうちにチャーリーは得た知能を保つ方法を必死に探しはじめた。
<br/>
<br/>
<!-- -->さて、どうなるでしょう？</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Debt, the first 5,000 years - David Graeber
**Debt, the firs...]]></title>
        <id>https://www.tomoliver.net/notes/2024-05-21-1533</id>
        <link href="https://www.tomoliver.net/notes/2024-05-21-1533"/>
        <updated>2024-05-21T16:26:05.832Z</updated>
        <summary type="html"><![CDATA[Debt, the first 5,000 years - David Graeber
Debt, the first 5,000 years - David Graeber
Its long! Exactly 400 pages, but what a book.
If there is just one book that I could read on economics/history/anthropology this would be it.
The ti...]]></summary>
        <content type="html"><![CDATA[<p><img src="/img/books/debt.jpg" alt="Debt, the first 5,000 years - David Graeber" normalWidth="300" normalHeight="400" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAKCAIAAAD3rtNaAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA30lEQVR4nGPYxGD+hFnyCIPJHgb7PQw2hxj0GRJnM/wUUf4vw/1fVv+/rPp/Wa3/Mtzewg4MXwTN/svZv/j04t2rRz9/vvkvIxqtF8nwQ9T9v23kxy9vf399/e//m/+ykpnqIQx/xF3+RyV/+PDi97sn378//S8rU8BgzfBfyvV/fOqb/x/evHly7+2D/5LyuQxyDP+lTf/Luf4+deDd3m23t6x9wSaQxMDA8F9SE+QGMHrLxXyRgSEYJCojDBd9w8l0hIHBn4GBYS2D5j4Gi60M5vMZpFoZGEoYGIwZGAAJqmEZV15u/wAAAABJRU5ErkJggg=="/></p>
<p><strong>Debt, the first 5,000 years - David Graeber</strong></p>
<p>Its long! Exactly 400 pages, but what a book.
If there is just one book that I could read on economics/history/anthropology this would be it.
The title sounds a lot more impenetrable than the content, when I tell people about this book I often get &quot;that sounds very academic&quot; but as a layman I found it perfectly readable.
<br/>
<br/>
<!-- -->This book takes a stab at the following questions:</p>
<br/>
<ul>
<li>What is the origin of money?</li>
<li>How is money created?</li>
<li>What is debt?</li>
<li>What is capitalism?</li>
<li>Where do markets come from?</li>
</ul>
<p><br/>
<!-- -->So it is by no means just about what many people think of when they hear the word &quot;debt&quot;.
<br/>
<br/>
<!-- -->The parts of this book that I enjoyed most were when the author got to show off his anthropological prowess. It really opened my eyes to the diversity (in the treatment of debt) of societies spanning millennia and continents. This includes the Tiv people of Africa, the Indus valley civilizations, the Irish kingdoms, ancient India and the usual suspects like the ancient Egyptians, Greeks, Romans, Chinese and so on.
Across world history there are trends that regardless of culture or geography make themselves apparent when considering what mediums of exchange were widely used at the time. You would think that knowledge of a particular society&#x27;s culture, economic system and geography would first need to be analysed before sufficient anthropological predictions could be made. However, the author makes the case that simply by determining whether a transaction was settled in cash, bartering or through credit, much can be gleaned about the sort of goods that could be acquired (e.g. slaves etc.).
<br/>
<br/>
<!-- -->One major point of the book is that we exist in the very early stages of an economic era that may yet span many hundreds of years, beginning in 1971 when the Dollar was taken of the gold standard. Across history, societies that use precious metals as the medium of exchange tended to have a propensity for war, expansion and enslavement. Societies that did not, tended to be more peaceful, however we don&#x27;t yet know if that trend will continue to be relevant in our modern era.
<br/>
<br/>
<!-- -->I must also mention that there is something that the author absolutely flogs a dead horse on. It is the total and thorough debunking of the idea that the inconvenience of barter provides the impetus for the advent of money. Every 15 pages or so we get a reminder of why the myth of barter is absurd and could not have been the primary means of exchange prior to money being established. Thank you David Graeber, I will personally guarantee that if I ever hear anyone repeat this myth I will forcefully point them in the direction of this book.
<br/>
<br/>
<!-- -->May you rest in peace.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[I've only been using Kagi for a couple of days now, but the ...]]></title>
        <id>https://www.tomoliver.net/notes/2024-05-08-0953</id>
        <link href="https://www.tomoliver.net/notes/2024-05-08-0953"/>
        <updated>2024-05-08T10:10:04.000Z</updated>
        <summary type="html"><![CDATA[I've only been using Kagi for a couple of days now, but the search results on programming topics are leagues better than Duckduckgo. I may be a little late to the party, but just realised that you can have shared state between pages without...]]></summary>
        <content type="html"><![CDATA[<p>I&#x27;ve only been using Kagi for a couple of days now, but the search results on programming topics are leagues better than Duckduckgo. I may be a little late to the party, but just realised that you can have shared state between pages without polluting <code>_app.tsx</code> in NextJS. I credit Kagi for taking me to this page where I learned about <code>getLayout</code>.
<a href="https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts#per-page-layouts">https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts#per-page-layouts</a>
<br/>
<br/>
<!-- -->Here is how to add Kagi as your default search engine to Firefox on NixOS (home-manager)</p>
<pre lang="nix"><code class="language-nix">home-manager.users.&lt;my-user&gt;.programs.firefox = {
  enable = true;
  profiles = {
    myProfile = {
      ...
      search = {
        force = true;
        default = &quot;Kagi&quot;;
        engines = {
          &quot;Kagi&quot; = {
            urls = [
              { 
                template = &quot;https://kagi.com/search?q={searchTerms}&quot;; 
              }
            ];
            iconUpdateURL = &quot;https://assets.kagi.com/v2/favicon-32x32.png&quot;;
            updateInterval = 24 * 60 * 60 * 1000; # every day
            definedAliases = [ &quot;@kg&quot; ];
        ...

</code></pre>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[The Trial - Franz Kafka
The Trial - Franz Kafka
This is ...]]></title>
        <id>https://www.tomoliver.net/notes/2024-04-07-1133</id>
        <link href="https://www.tomoliver.net/notes/2024-04-07-1133"/>
        <updated>2024-04-08T11:10:17.000Z</updated>
        <summary type="html"><![CDATA[The Trial - Franz Kafka
The Trial - Franz Kafka
This is one one of those books that people tell you to read when they actually haven't finished it themselves (you know who you are). I don't think anyone would mind me saying that its a p...]]></summary>
        <content type="html"><![CDATA[<p><img src="/img/books/the-trial.jpg" alt="The Trial - Franz Kafka" normalWidth="260" normalHeight="346" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAKCAIAAAD3rtNaAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA50lEQVR4nAHcACP/AFFRUaOjo76+vsXFxb29vbu7u1VVVQBYWFhzc3NhZ2d6enppaGiCgoJ1dXUAZWVlpaWlm25trqeksLu7r6+vdXV1AGppabq6uqpGS6MVL69JU83O0YuLiQBzc3PBwcHFtrbNp6rHoaLLy8upqakAjo6O0tLSzdHRy9PT7/f36enp1tbWAKGhoefn593b25COjmdlZdra2szMzACqqqr5+fn39/enp6cCAgLOzs7GxsYAsLCw/Pz8/Pz8sbGxLi4u4eHhzs7OALW0tP7+/vHx8cjIyB4eHqqqqtra2qfNiJ5k3jevAAAAAElFTkSuQmCC"/></p>
<p><strong>The Trial - Franz Kafka</strong></p>
<p>This is one one of those books that people tell you to read when they actually haven&#x27;t finished it themselves (you know who you are). I don&#x27;t think anyone would mind me saying that its a painful read, because surely that was the author&#x27;s intention. It&#x27;s about a guy who lives in a world that is governed by an impenetrable court system, the workings of which nobody understands. He gets arrested randomly and put on trial without explanation. Nobody knows what the charges are (except maybe the high judges?) so its very difficult to defend himself. There is what they call an &quot;advocate&quot; who offers to help the protagonist with the trial. However the help he provides consists of various sycophantic offerings to court judges who happened to have been acquaintances from the past. The protagonist also finds himself entangled with various female characters with tenuous connections to the courts, who for some reason show great romantic interest in him. They offer to help in one way or another by providing the protagonist with some sort of 3rd-hand information about the courts, which nevertheless fails to translate to much advantage in the proceedings. The setting of the book is no doubt dystopian, and reminds me of the Maoist witch hunts described in the book <a href="https://en.wikipedia.org/wiki/Wild_Swans">Wild Swans</a>. For me it served as an unsettling reminder that there are people in this world who would quite happily construct such an opaque bureaucracy as described in the book. If the consequences of the court&#x27;s ruling were not so dire, it could even be interpreted as comedic, but the sinister aspect of it all seems a little too real and a little too plausible for it to make me laugh.</p>
<p><br/>
<!-- -->I may have been a bit harsh. Overall I am glad to have read this book.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[人間失格　太宰治
人間失格　太宰治
日本人なら誰でも知ってる小説だと聞いて読もうと思った一冊。（読んだ後に友達に...]]></title>
        <id>https://www.tomoliver.net/notes/2024-03-09-1150</id>
        <link href="https://www.tomoliver.net/notes/2024-03-09-1150"/>
        <updated>2024-03-09T13:08:45.000Z</updated>
        <summary type="html"><![CDATA[人間失格　太宰治
人間失格　太宰治
日本人なら誰でも知ってる小説だと聞いて読もうと思った一冊。（読んだ後に友達に確認したらそれは大げさだったらしい。笑）
内容はとても暗く、絶望感がすごく伝わってくる。
落ち込んでる時に読ない方がいいと注意されていたけど、自分は落ち込んでいる時に読んでも大丈夫だったかもしれない。
描写が抽象的で「何が起きた？具体的に言ってくれ！」みたいなところが多かったけど、文学って言うのはそんなもんだろう。
主人公の葉蔵は変わりたくて...]]></summary>
        <content type="html"><![CDATA[<p><img src="/img/books/ningen-shikkaku.jpg" alt="人間失格　太宰治" normalWidth="205" normalHeight="293" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAKCAIAAAD3rtNaAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA50lEQVR4nAHcACP/APD9/7jM5/Gpi4+QcZqhhJCFZ7y6pACvzPBDfLxRIBkDAwARHBcqEQCDb1AA8smmpGgnLColPT1BLS0wOyUAsoViAP/CbbhbEiAZDiUuNBsdGy8fALeFYAD9x3etbBaUZlZVMx8WLycsMheReVwAya+GYVYw3GgcspObOG1xEkBCZW5SAPa/eEhGGs1uG6dZPxU/OwJVgUB/dwD/y29iQBHfYBrYNQ9ONxNkUFG/gmoA/6dw3iAO4CIO3CAN4iIP1TsZ/3VHAP/Fo+lkM/V6R/F2QfF0Qu1lPf+ifBp7WVrnIEyNAAAAAElFTkSuQmCC"/></p>
<p><strong>人間失格　太宰治</strong></p>
<p>日本人なら誰でも知ってる小説だと聞いて読もうと思った一冊。（読んだ後に友達に確認したらそれは大げさだったらしい。笑）<br/>
<!-- -->内容はとても暗く、絶望感がすごく伝わってくる。<br/>
<!-- -->落ち込んでる時に読ない方がいいと注意されていたけど、自分は落ち込んでいる時に読んでも大丈夫だったかもしれない。<br/>
<!-- -->描写が抽象的で「何が起きた？具体的に言ってくれ！」みたいなところが多かったけど、文学って言うのはそんなもんだろう。<br/>
<!-- -->主人公の葉蔵は変わりたくてもアル中から脱することができなくて、最後の方にお酒をやめたけど結局薬物依存症に移っただけだった。<br/>
<!-- -->現在はインターネット依存症、スマホ依存等が広がる一方、少なくとも誰もが一人ぐらい依存症を持ってる知り合いがいるんじゃないかなって思う。<br/>
<!-- -->自分だってインターネット依存症だもん。笑。<br/>
<!-- -->主人公の葉蔵はいつも周りに人がいても素でいられなかったから孤独だったと思う。<br/>
<!-- -->私達がLINEで友達とやり取りしていても感じる孤独と似てると思う。<br/>
<br/>
<!-- -->心に響いた箇所</p>
<ul>
<li>女は死んだように深く眠る、女は眠るために生きてるのではないかしら。</li>
<li>自分は道化を演じ、男はさすがにいつまでもゲラゲラ笑ってもいませんし、それに自分も男の人に対し、調子に乗ってあまりお道化を演じすぎると失敗すると言う事を知っていましたので、必ず適当のところで切り上げるように心掛けていましたが、女は適度という事を知らず、いつまでもいつまでも、自分にお道化を要求し、自分はその限りないアンコールに応じて、へとへとになるのでした。</li>
<li>自分は、女があんなに急に泣き出したりした場合、何か甘い物を手渡してやると、それを食べて機嫌を直すという事だけは、幼い時から、自分の経験によって知っていました。</li>
<li>俗にいうチャッカリ性でした。田舎者の自分が、愕然と眼を見張ったくらいの、冷たく、ずるいエゴイズムでした。自分のように、ただ、とめどなく流れるたちの男では
無かったのです。</li>
<li>「モチよ」</li>
</ul>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[I just happened to stumble across The Dandy Warhols' new son...]]></title>
        <id>https://www.tomoliver.net/notes/2024-02-21-1344</id>
        <link href="https://www.tomoliver.net/notes/2024-02-21-1344"/>
        <updated>2024-02-21T14:29:10.000Z</updated>
        <summary type="html"><![CDATA[I just happened to stumble across The Dandy Warhols' new song - “I’d Like To Help You With Your Problem (feat. Slash)” and its pretty good. 
The music video is mostly AI generated, with all the weird/creepy artifacts and distortions you may...]]></summary>
        <content type="html"><![CDATA[<p>I just happened to stumble across <a href="https://www.youtube.com/watch?v=H3H2AVm1uD8">The Dandy Warhols&#x27; new song - “I’d Like To Help You With Your Problem (feat. Slash)”</a> and its pretty good.
<br/>
<br/>
<!-- -->The music video is mostly AI generated, with all the weird/creepy artifacts and distortions you may expect.
A genius move since this is exactly the dreamy, hazy, vague, feeling their music has always conveyed to me.
<br/>
<br/>
<!-- -->Its amazing how over all the years of cinema, humanities attempts to convey the sensation of dreaming have never even came close to what AI can do. As the underlying models improve, we are likely to see fewer of these hallucinations in generated content, which I find a little bit sad.
I love the mental image of a giant machine, lying motionless in deep slumber, engineers prodding at the wires in its brain, hoping to influence the direction it takes as it drifts through a dream.
<br/>
<br/>
<!-- -->I like this analogy because explains why AI halucinates.
<br/>
<!-- -->What do we call it when we halucinate in our sleep? ...A dream.
<br/>
<br/>
<!-- -->It reminds me of the anime <a href="https://www.imdb.com/title/tt26737616/">pluto</a> which I saw on netflix.
The plot is based on our desire to create a robot with a more human mind, with scientists going to ever greater lengths to balance human-like emotions with the cold hard logic expected of machines. One scientist takes it a step too far by creating the &quot;ultimate&quot; robot by simulating the lives of billions of humans in its brain. But there was one problem, the robot would not wake up. It was in an endless dream. Eventually the scientist found a way to wake up the robot by forcing an overwhelming emotion into its brain, rage.
<br/>
<br/>
<!-- -->Oof, lets not do that.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[世界音痴 穂村弘
世界音痴 穂村弘
明らかにペルソナじゃないけど頑張って読んでみた。
短歌について何もわからない...]]></title>
        <id>https://www.tomoliver.net/notes/2024-02-21-1243</id>
        <link href="https://www.tomoliver.net/notes/2024-02-21-1243"/>
        <updated>2024-02-22T11:55:02.000Z</updated>
        <summary type="html"><![CDATA[世界音痴 穂村弘
世界音痴 穂村弘
明らかにペルソナじゃないけど頑張って読んでみた。
短歌について何もわからないし、調べても読めるものは一つも見つからなかった。それでもこの本はすごく面白かった。短歌と難しい日本語は通じなかったけど昔から「世界音痴」を体験してる人だから、なんとなく作者の気分がわかる。
ある部分は僕とあんまりにも似すぎて、それなりの恐怖感を感じた。このままじゃ自分も３９歳になっても実家に住んでるだろうって。
読んで共感したポイントが山ほどあったけれど、特...]]></summary>
        <content type="html"><![CDATA[<p><img src="/img/books/sekai-onchi.jpg" alt="世界音痴 穂村弘" normalWidth="346" normalHeight="500" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAKCAIAAAD3rtNaAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA50lEQVR4nAHcACP/AMWnm72ObbmOgo5yYpt4YpJtWIJnVgDUxMvcytLl1umff36MeW+tpqOjkYMAyMDNzs3W6KK0vAA81E10r7LStLHMAL6Nm9O+r6WEk79iKe+0ANTNI8PFBgC4m5SsmYoxQG2DqgDu/x/m/x3R8gQAbWhvpZWVuYCfoZwA7/8f5v8c0u4AAFtfbvnS1sAARvnHp5OXWyouFXV+bQAgK0ODdXLjrZ/v0tLGrLJgXHZCaqgAChgwVE1N58vH/+nd2r54wq+G6t3iAAAAIiw4Tr+tsum/yKOLUubEOv/lof6YeTlcWQTMAAAAAElFTkSuQmCC"/></p>
<p><strong>世界音痴 穂村弘</strong></p>
<p>明らかにペルソナじゃないけど頑張って読んでみた。
短歌について何もわからないし、調べても読めるものは一つも見つからなかった。それでもこの本はすごく面白かった。短歌と難しい日本語は通じなかったけど昔から「世界音痴」を体験してる人だから、なんとなく作者の気分がわかる。
ある部分は僕とあんまりにも似すぎて、それなりの恐怖感を感じた。このままじゃ自分も３９歳になっても実家に住んでるだろうって。
<br/>
<br/>
<!-- -->読んで共感したポイントが山ほどあったけれど、特に心に響いた文のみに絞る。
<br/>
<br/>
<!-- -->(降順)</p>
<ol>
<li>金額に合わせて小銭を揃えるという、その時間に耐えられないのである。面倒なのではない。店員を待たせている時間そのものが熱湯のような痛みを伴って感じられるのだ。そしてお釣りを受け取ると、ばっとコートのポケットに投げ込んでしまう。今度は受け取ったお釣りを財布に収納する時間に耐えられないのである。別に私の背後に客が並んでいるわけではない。せめてレシートだけでもその場で捨てたいのだが、それができない。私のポケットには財布の外側に常に大量の小銭とレシートが溢れていて気持ちが悪い。</li>
<li>表面が白っぽくなった大トロのパック（半額）を手にとって買おうか買うまいか、得か損か、まだまだうまいかもう腐りかけか、迷っているとき、不意に「ああっ」と叫びたくなる。「人生って、これで全部なのか」</li>
<li>＜親密さ＞をそっくり残したままの、恋の終わりは苦しい。「たからもののシャツ、うちにあるよ」「うん」「送ろうか」「うん」「たからものなの？」。それは、いつもの二人の、変わりなく親密なやり取りでありながら、同時に恋の終わりの会話なのだ。</li>
<li>「今日こそ蜂蜜を買うぞ」「おー」と勇んで蜂蜜屋に直行する。「いらっしゃいませ」と迎えてくれた店員は人間の女性だった。棚に並んだ何種類もの蜂蜜の色は微妙に違う。中の一つに私が手を伸ばすと、「それは小心者向きでございません」と店員さんの声。驚いて瓶から手を放す。</li>
</ol>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[So this year I thought I'd start recording the books I read....]]></title>
        <id>https://www.tomoliver.net/notes/2024-01-27-1028</id>
        <link href="https://www.tomoliver.net/notes/2024-01-27-1028"/>
        <updated>2024-01-27T10:49:20.000Z</updated>
        <summary type="html"><![CDATA[So this year I thought I'd start recording the books I read.
Mainly so I don't get to the end of the year and think "What did I read?".
The first book that I have read this year is called:
陽気なギャングが地球を回す
面白かった。伏線回収がすごい本だった。
普通は強盗系の話とかはあん...]]></summary>
        <content type="html"><![CDATA[<p>So this year I thought I&#x27;d start recording the books I read.
Mainly so I don&#x27;t get to the end of the year and think &quot;What did I read?&quot;.</p>
<p><br/>
<!-- -->The first book that I have read this year is called:</p>
<p><strong>陽気なギャングが地球を回す</strong>
<br/>
<br/>
<!-- -->面白かった。伏線回収がすごい本だった。
普通は強盗系の話とかはあんまり好きじゃないけどこの本は面白かった。
信じ難い部分も多かったけどあらすじは複雑で予想できなかったから最後まで読んで楽しかった。
いつかはまた読みたいと思った。</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[First impressions of Remix vs. Nextjs (pages router)

O...]]></title>
        <id>https://www.tomoliver.net/notes/2024-01-26-0938</id>
        <link href="https://www.tomoliver.net/notes/2024-01-26-0938"/>
        <updated>2024-01-26T10:23:29.000Z</updated>
        <summary type="html"><![CDATA[First impressions of Remix vs. Nextjs (pages router)

Obviously Remix is the newer kid on the block, so expect library support etc. to be less comprehensive.
It seems that Remix has changed its routing system recently. Unfortunatel...]]></summary>
        <content type="html"><![CDATA[<p><strong>First impressions of Remix vs. Nextjs (pages router)</strong></p>
<ul>
<li>Obviously Remix is the new<em>er</em> kid on the block, so expect library support etc. to be less comprehensive.</li>
<li>It seems that Remix has changed its routing system recently. Unfortunately many of the tutorials out there use the old one (directory based), which is confusing.</li>
<li>Remix is less of a black box than Next. You are allowed to configure the <code>entry.client.tsx</code> and <code>entry.server.tsx</code> in any way you want to. This lets you take control of exactly how you do SSR and hydration.</li>
<li>At the time of writing, Remix heavily leans into using the latest React features like <code>renderToPipeableStream</code> which you will see in your <code>entry.server.tsx</code>. Unfortunately a lot of libraries are not up to date and still use <code>renderToString</code> in examples, like <a href="https://github.com/mui/material-ui/blob/master/examples/material-ui-remix-ts/app/entry.server.tsx">mui</a>. So it can take a while to figure out how to integrate all of your libraries so that they are happy doing SSR.</li>
<li>Remix is a framework who&#x27;s main abstraction is the data fetching on the server side. Nextjs (page router) has a similar concepts with <code>getServerSideProps</code>, <code>getInitialProps</code> etc, but it seems that Remix&#x27;s solution is more all encompassing and unifying.</li>
<li>Nextjs is in weird limbo state at the moment where you have to choose between the <code>pages</code> router and the <code>app</code> router when you create your project. This is fine if you are sure exactly what feature you need, but in the case where you don&#x27;t really know, Remix might be a safer bet.</li>
</ul>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Nx hanging
Ok I'm using Nx as a monorepo manager for a p...]]></title>
        <id>https://www.tomoliver.net/notes/2024-01-16-0948</id>
        <link href="https://www.tomoliver.net/notes/2024-01-16-0948"/>
        <updated>2024-01-16T10:49:13.000Z</updated>
        <summary type="html"><![CDATA[Nx hanging
Ok I'm using Nx as a monorepo manager for a project and got into to some very strange behavior. There are several node apps in the repo and to run them, you use npx nx serve <my-app-name>. Sometimes tho, after updating an n...]]></summary>
        <content type="html"><![CDATA[<p><strong>Nx hanging</strong></p>
<p>Ok I&#x27;m using <a href="https://nx.dev/">Nx</a> as a monorepo manager for a project and got into to some very strange behavior. There are several node apps in the repo and to run them, you use <code>npx nx serve &lt;my-app-name&gt;</code>. Sometimes tho, after updating an npm package I would get an error like:</p>
<pre><code>LOCK-FILES-CHANGED

please restart nx daemon using `nx reset`
</code></pre>
<p>Unfortunately I couldn&#x27;t reproduce the error message exactly at the time of writing, so this is the best I could remember.<br/>
<br/>
<!-- -->Anyway, no matter how many times I run <code>nx reset</code>, my <code>npx nx serve</code> commands would just hang. Zero output at all. As I suspected the daemon, I tried to view its logs.
To find out where the log file was I did:</p>
<pre><code>$ npx nx daemon
Nx Daemon is currently running:
  - Logs: /home/tom/Documents/project/node_modules/.cache/nx/d/daemon.log
  - Process ID: 33785
</code></pre>
<p>But when I tried to <code>cat</code> the log file it apparently did not exist!</p>
<p>So presumably the daemon was in some kind of infinite death loop...
I never got to the bottom of the cause, but did manage to find a solution.<br/>
<br/>
<!-- -->There is an environment variable that disables the daemon because its more of a performance optimisation than a critical feature.
So now I just <code>export NX_DAEMON=false</code> and everything seems to work ok.<br/>
<br/>
<!-- -->If you use a <code>nix</code> <code>devShell</code> like me you can just add it to the <code>shellHook</code> like so:</p>
<pre lang="nix"><code class="language-nix">...
devShells.default = pkgs.mkShell {
  shellHook = &#x27;&#x27;
    export NX_DAEMON=false
  &#x27;&#x27;;
};
...
</code></pre>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Have you heard of semantic Javascript ?
You probably answe...]]></title>
        <id>https://www.tomoliver.net/notes/2023-12-12-1510</id>
        <link href="https://www.tomoliver.net/notes/2023-12-12-1510"/>
        <updated>2023-12-12T16:43:15.000Z</updated>
        <summary type="html"><![CDATA[Have you heard of semantic Javascript ?
You probably answered "No" because I just thought of it 😇
Everyone knows they should be writing semantic HTML but why not apply the same standards to a "real" programming language.
Semantic program...]]></summary>
        <content type="html"><![CDATA[<p>Have you heard of <em>semantic</em> Javascript ?</p>
<p>You probably answered &quot;No&quot; because I just thought of it 😇</p>
<p>Everyone knows they should be writing semantic HTML but why not apply the same standards to a &quot;real&quot; programming language.</p>
<p>Semantic programming is all about using the constructs a language provides in such a way as to convey intent to the target audience, whether that be human or machine. In HTML we have the classic example of using the <code>&lt;footer&gt;</code> tag instead of just spamming <code>&lt;div&gt;</code> everywhere. Lets apply that logic to Javascript.</p>
<p>Here&#x27;s some non-semantic JS.</p>
<pre lang="js" messages="[object Object],[object Object]"><code class="language-js">let arr = [1, 2, 3]
for (let i = 0; i &lt; arr.length; i++) {
  arr[i] = arr[i] * 2
}
</code></pre>
<p>now for something semantic</p>
<pre lang="js" style="--message-margin:0.2rem" messages="[object Object]"><code class="language-js">// we are doing the exact same thing as before, just better.
let arr = [1, 2, 3]
arr = arr.map((x) =&gt; x * 2)
</code></pre>
<p>I think you get the idea...</p>
<p>But just in case, here is another scenario...</p>
<pre lang="js" messages="[object Object]"><code class="language-js">while (i &lt; arr.length) {
  ...
  // do some operation
  res = ...
}
</code></pre>
<p>Compare the above to this:</p>
<pre lang="js" style="--message-margin:0.2rem" messages="[object Object]"><code class="language-js">let res = arr.reduce((acc, cur) =&gt; {
  ...
  // do some operation
},{})
</code></pre>
<p>So.. what do you think?</p>
<p>Ok, maybe this is just an excuse to get you to do some functional programming.</p>
<p>You got me! 😘</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Here's how to burn audio to a CD from the command line.
```s...]]></title>
        <id>https://www.tomoliver.net/notes/2023-11-25-2149</id>
        <link href="https://www.tomoliver.net/notes/2023-11-25-2149"/>
        <updated>2023-11-25T22:03:08.000Z</updated>
        <summary type="html"><![CDATA[Here's how to burn audio to a CD from the command line.

# Remove spaces from filenames
for f in *; do mv "$f" `echo $f | tr ' ' '_'`; done
# Convert all tracks to wav
for i in $( ls ); do ffmpeg -i $i $i.wav; done
# No...]]></summary>
        <content type="html"><![CDATA[<p>Here&#x27;s how to burn audio to a CD from the command line.</p>
<pre lang="shell"><code class="language-shell"># Remove spaces from filenames
for f in *; do mv &quot;$f&quot; `echo $f | tr &#x27; &#x27; &#x27;_&#x27;`; done
# Convert all tracks to wav
for i in $( ls ); do ffmpeg -i $i $i.wav; done
# Normalize the volume across all tracks
normalize -m *.wav
# Burn to disk
sudo wodim -v -dev=&#x27;/dev/cdrom&#x27; -audio -pad *.wav
</code></pre>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[So this is one of the first times I have actually come acros...]]></title>
        <id>https://www.tomoliver.net/notes/2023-11-25-1626</id>
        <link href="https://www.tomoliver.net/notes/2023-11-25-1626"/>
        <updated>2023-11-25T23:13:13.000Z</updated>
        <summary type="html"><![CDATA[So this is one of the first times I have actually come across a hurdle when it comes to developing software on old computers. 
A couple days ago I started experimenting with bun, which is amazing by the way. One of the big selling points is...]]></summary>
        <content type="html"><![CDATA[<p>So this is one of the first times I have actually come across a hurdle when it comes to developing software on old computers.
A couple days ago I started experimenting with <a href="https://github.com/oven-sh/bun">bun</a>, which is amazing by the way. One of the big selling points is that it is <em>really</em> fast, but this speed seems to come at a cost...</p>
<p>As I use NixOS, I installed it the usual way by adding it to my <code>configuration.nix</code>. But alas, life isn&#x27;t always simple.
It seems to install ok, but when I tried to run <code>bun</code> I got an error immediately:</p>
<pre lang="sh"><code class="language-sh">illegal hardware instruction (core dumped)
</code></pre>
<p>Wow an actually hardware error! I haven&#x27;t seen one of those since I was trying some overclocking! So the problem comes down to the fact that <code>bun</code> uses some cutting edge CPU instructions that weren&#x27;t around in 2012. Now, on other Linux distributions you can simply use a bash script to install bun à la:</p>
<pre lang="sh"><code class="language-sh">curl -fsSL https://bun.sh/install | bash
</code></pre>
<p>Apparently it checks your CPU and in the event it finds something of a certain vintage it installs a <code>baseline</code> version of <code>bun</code> which I guess doesn&#x27;t use any of the funky CPU instructions it would usually. Unfortunately the nix package does not at the time of writing do this. <a href="https://github.com/NixOS/nixpkgs/pull/203379">There is actually a PR open to fix this</a> but it has not been merged yet. So for the time being you can basically just take the code from the PR, save it to a file locally and build the <code>baseline</code> version yourself.</p>
<ol>
<li><a href="https://raw.githubusercontent.com/NixOS/nixpkgs/3975fb8a5d80e486aad7f030b9937eb56531a698/pkgs/development/web/bun-baseline/default.nix">Download and save this file.</a></li>
<li>Now to build is, run:</li>
</ol>
<pre lang="sh"><code class="language-sh">nix-build -E &#x27;with import &lt;nixpkgs&gt; {}; callPackage ./default.nix {}&#x27;
</code></pre>
<ol start="3">
<li>You should now be able to run <code>bun</code> by doing:</li>
</ol>
<pre lang="sh"><code class="language-sh">./result/bin/bun
</code></pre>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[I witnessed this absolute corker of a bug in the office toda...]]></title>
        <id>https://www.tomoliver.net/notes/2023-11-22-2203</id>
        <link href="https://www.tomoliver.net/notes/2023-11-22-2203"/>
        <updated>2023-11-22T22:23:52.000Z</updated>
        <summary type="html"><![CDATA[I witnessed this absolute corker of a bug in the office today.
After a force reboot (its a long story), we noticed that the cursor was flickering on my colleague's Ubuntu laptop. After some googling, we realised that this flickering only ha...]]></summary>
        <content type="html"><![CDATA[<p>I witnessed this absolute corker of a bug in the office today.</p>
<p>After a force reboot (its a long story), we noticed that the cursor was flickering on my colleague&#x27;s Ubuntu laptop. After some googling, we realised that this flickering only happens at 200% fractional scaling. Setting to any other value like 175%, 150% etc. works perfectly fine.</p>
<p><a href="https://askubuntu.com/questions/1234189/cursor-flickers-on-primary-display-when-fractional-scaling-is-enable-for-dual-mo">https://askubuntu.com/questions/1234189/cursor-flickers-on-primary-display-when-fractional-scaling-is-enable-for-dual-mo</a></p>
<p>Despite the link above being more than <strong>three and a half years old,</strong> it appears that <em>even today</em>, this bug can still be observed in the wild, in its natural habitat, on Ubuntu.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[It's taken me far too long to discover this foot-gun in expr...]]></title>
        <id>https://www.tomoliver.net/notes/2023-11-21-1657</id>
        <link href="https://www.tomoliver.net/notes/2023-11-21-1657"/>
        <updated>2023-11-21T17:12:41.000Z</updated>
        <summary type="html"><![CDATA[It's taken me far too long to discover this foot-gun in express. 
I started a new express project the other day and realised that my request bodies were all empty! Spent a good hour thinking  it was a problem in the client or the proxy etc....]]></summary>
        <content type="html"><![CDATA[<p>It&#x27;s taken me far too long to discover this foot-gun in express.</p>
<p>I started a new express project the other day and realised that my request bodies were all empty! Spent a good hour thinking  it was a problem in the client or the proxy etc... but was actually just because I forgot to apply these two middlewares.</p>
<p>So the question is, why are these middlewares not enabled by default????</p>
<pre lang="js"><code class="language-js">  const app = express()
  // this populates req.body when the payload is json
  app.use(express.json({ type: &quot;application/json&quot; }))
  // this populates req.body when the payload is urlencoded 
  // (e.g when a form gets submitted)
  app.use(express.urlencoded({ extended: true }))
  ...

</code></pre>
<p>Sensible defaults are just something we don&#x27;t deserve I guess...</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Optimistic VS Pessimistic Locking
Imagine you are on a p...]]></title>
        <id>https://www.tomoliver.net/notes/2023-11-17-1753</id>
        <link href="https://www.tomoliver.net/notes/2023-11-17-1753"/>
        <updated>2023-11-18T14:37:46.000Z</updated>
        <summary type="html"><![CDATA[Optimistic VS Pessimistic Locking
Imagine you are on a plane and you need to pee.
You're a stubborn guy. When you leave your seat, you are determined to pee one way or another.
You must assume your fellow passengers are as least as stub...]]></summary>
        <content type="html"><![CDATA[<p><strong>Optimistic VS Pessimistic Locking</strong></p>
<p>Imagine you are on a plane and you need to pee.
You&#x27;re a stubborn guy. When you leave your seat, you are determined to pee one way or another.
You must assume your fellow passengers are as least as stubborn.</p>
<p><strong>Optimistic Locking</strong></p>
<p>There is a single toilet located in the middle of the plane that can be accessed from multiple directions.
You are sitting at the back and can only see the entrance to the toilet that is in your direct line of sight.
You want to know if its ok to go to the toilet so you check the overhead light which says &quot;vacant&quot;.
Assuming that it will remain vacant for the entirety of the time it will take you to reach your destination, you get out of your seat and commit to peeing.
Now one of two things are going to happen:</p>
<ol>
<li>The toilet is vacant by the time you get to it - Success</li>
<li>Someone else has slipped into the toilet via an entrance you couldn&#x27;t see during the time it took you to walk there. Since you have committed to peeing, you have no choice but to do so in a cup - Failure.</li>
</ol>
<p><strong>Pessimistic Locking</strong></p>
<p>Due to your toilet paranoia you reserve a seat on a specially designed plane. It is designed such that each seat is directly adjacent to a toilet and has excellent visibility of all its entrances. Because of this, you are able to in a single instant, both check that a toilet is free <em>and</em> enter it. It is therefore guaranteed that you nor any other passenger will be forced to pee in a cup.</p>
<p>So obviously there is a dilemma here.</p>
<p>The specially designed plane will carry less passengers due to its emphasis on providing the stubborn peeing passenger peace of mind.
The normal plane will be more efficient but has to account for the occasional cup of pee getting knocked over.</p>
<p><strong>What have we learned?</strong></p>
<p>There is no &quot;correct&quot; way to design a plane, only trade-offs.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Some lesser known nice things about Japan 🗾

Ambulances ar...]]></title>
        <id>https://www.tomoliver.net/notes/2023-08-11-0447</id>
        <link href="https://www.tomoliver.net/notes/2023-08-11-0447"/>
        <updated>2023-08-11T04:47:19.000Z</updated>
        <summary type="html"><![CDATA[Some lesser known nice things about Japan 🗾

Ambulances are loud, but not too loud.
When Police cars do unexpected things like making a U-turn suddenly, they politely inform everyone nearby with a megaphone.
When large vehicles make...]]></summary>
        <content type="html"><![CDATA[<p>Some lesser known nice things about Japan 🗾</p>
<ul>
<li>Ambulances are loud, but not <em>too</em> loud.</li>
<li>When Police cars do unexpected things like making a U-turn suddenly, they politely inform everyone nearby with a megaphone.</li>
<li>When large vehicles make a turn near a pedestrian they play a vocal warning &quot;Watch out! I&#x27;m about to turn left!&quot;</li>
<li>Dog owners carry a bottle of water to wash away any pee the dog might do on a walk.</li>
<li>There are lots of outside gyms.</li>
<li>There are lots of summer festivals which aren&#x27;t exclusively an excuse to get drunk.</li>
<li>Its not okay to be ugly.<!-- -->
<ul>
<li>You better look your best if you&#x27;re thinking about leaving your home.</li>
<li>Foreigners usually get the benefit of the doubt.</li>
</ul>
</li>
<li>Lots of stuff made in Japan for Japanese people only. (Films, cars, appliances...)</li>
<li>There is one and only one right way to do everything.<!-- -->
<ul>
<li>If you order something in a cafe or a restaurant, chances are they&#x27;ll tell you the right way to consume it.</li>
<li>e.g. You order an iced coffee creamy thing from a local cafe.<!-- -->
<ol>
<li>First taste the top and bottom layers independently before mixing them both together with the straw...</li>
</ol>
</li>
</ul>
</li>
<li>There is a lot of wildlife everywhere.<!-- -->
<ul>
<li>Probably more wildlife in Tokyo than in any National park in the UK.</li>
</ul>
</li>
<li>There are dry ice machines in some supermarkets.</li>
<li>Some cars have a horn? that says in a polite voice &quot;Please watch out, a car is passing by!&quot; to unsuspecting pedestrians that haven&#x27;t noticed.</li>
<li>Relaxing background music automatically starts playing the instant your skin comes into contact with the toilet seat.</li>
<li>Safety first<!-- -->
<ul>
<li>At petrol stations there is an &quot;anti static electricity pad&quot; for you to touch before filling up your car.</li>
</ul>
</li>
<li>The sky is big.
<img src="/img/japan-cables-sky.png" alt="Power cables on a hot sunny day" normalWidth="209" normalHeight="281" blur="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAKCAYAAAB4zEQNAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA+ElEQVR4nCXHTSsDAACA4ffg5ODCQSsctnxmSmKrxYFok+akcBlJJszdWikXu0gttSyJw0pLK7WUWjmstOLihHLwMxy8isNzeCCQk9Cp9BelZU66kxLKSE9WCBaktyiD59KZkfC+xA7/T/BM+i5ksCTDlxI+sjWyJuGy0HslA2XpKkj3qXRs2j62LJFbYehGQteSrjuRe5bAjpHEuizVhdGajFTNV778/lHGC7ZNpSVVFWJPEm3I9L0s3knwRGJZF/ZKQvxV5l9lpinRmkw+yFTJvYNjYfXdP6kPSdZl5U3iDZdS20L6U/a/ZPdFtpqy8ehOvmI8Mesv1pWH5NW7RpsAAAAASUVORK5CYII="/></li>
</ul>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Its been a while since I did any data modelling in typescrip...]]></title>
        <id>https://www.tomoliver.net/notes/2023-03-06-1718</id>
        <link href="https://www.tomoliver.net/notes/2023-03-06-1718"/>
        <updated>2023-03-06T19:18:19.000Z</updated>
        <summary type="html"><![CDATA[Its been a while since I did any data modelling in typescript.

// An example of a user management system
// Define user status type, just an enum
type UserAccountStatus = "ACTIVE" | "DORMANT" | "DELETED"
// Define a base typ...]]></summary>
        <content type="html"><![CDATA[<a class="u-in-reply-to" href=https://www.tomoliver.net/notes/2023-03-03-2144>
      In reply to: https://www.tomoliver.net/notes/2023-03-03-2144
      </a><p>Its been a while since I did any data modelling in typescript.</p>
<pre lang="ts"><code class="language-ts">// An example of a user management system
// Define user status type, just an enum
type UserAccountStatus = &quot;ACTIVE&quot; | &quot;DORMANT&quot; | &quot;DELETED&quot;
// Define a base type
type BaseUser = { name: string; status: UserAccountStatus }
// Create concrete types using the base type
type ActiveUser = BaseUser &amp; { status: &quot;ACTIVE&quot; }
type DormantUser = BaseUser &amp; { status: &quot;DORMANT&quot; }
type DeletedUser = BaseUser &amp; { status: &quot;DELETED&quot; }
type User = ActiveUser | DormantUser | DeletedUser
// Create the type of function we want to implement
// Make return type a promise because we talk to the DB
type DeleteUser = (user: ActiveUser | DormantUser) =&gt; Promise&lt;DeletedUser&gt;
// do a temporary implementation of the function
const deleteUser: DeleteUser = (user) =&gt; {
  const deletedUser: DeletedUser = { ...user, status: &quot;DELETED&quot; }
  // define a helper that we haven&#x27;t got yet
  return writeUserToDB(deletedUser)
}
// declare the type of the helper like so:
declare function writeUserToDB&lt;A extends User&gt;(user: A): Promise&lt;A&gt;

</code></pre>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Bought some books with the intention of learning Chinese but...]]></title>
        <id>https://www.tomoliver.net/notes/2023-03-05-2234</id>
        <link href="https://www.tomoliver.net/notes/2023-03-05-2234"/>
        <updated>2023-03-05T22:39:43.000Z</updated>
        <summary type="html"><![CDATA[Bought some books with the intention of learning Chinese but yeah its pretty hard.
As long as I don't give up completely I might be ok at it in the region of say... a decade?
Which is really not too long when you think about it.
So I ...]]></summary>
        <content type="html"><![CDATA[<p>Bought some books with the intention of learning Chinese but yeah its pretty hard.<br/>
<!-- -->As long as I don&#x27;t give up completely I might be ok at it in the region of say... a decade?<br/>
<!-- -->Which is really not too long when you think about it.<br/>
<!-- -->So I guess nothing to complain.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[https://www.youtube.com/watch?v=I9gLrRwLFCs
Tonight is city ...]]></title>
        <id>https://www.tomoliver.net/notes/2023-03-03-2144</id>
        <link href="https://www.tomoliver.net/notes/2023-03-03-2144"/>
        <updated>2023-03-05T19:14:26.000Z</updated>
        <summary type="html"><![CDATA[https://www.youtube.com/watch?v=I9gLrRwLFCs
Tonight is city pop vibes 🗾]]></summary>
        <content type="html"><![CDATA[<a class="u-in-reply-to" href=https://www.tomoliver.net/notes/2023-02-12-1940>
      In reply to: https://www.tomoliver.net/notes/2023-02-12-1940
      </a><p><a href="https://www.youtube.com/watch?v=I9gLrRwLFCs">https://www.youtube.com/watch?v=I9gLrRwLFCs</a></p>
<p>Tonight is city pop vibes 🗾</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[nice]]></title>
        <id>https://www.tomoliver.net/notes/2023-03-03-2042</id>
        <link href="https://www.tomoliver.net/notes/2023-03-03-2042"/>
        <updated>2023-03-03T20:43:33.000Z</updated>
        <summary type="html"><![CDATA[nice]]></summary>
        <content type="html"><![CDATA[<a class="u-in-reply-to" href=https://blog.rubenwardy.com/2022/11/16/thinkpad-x1-fingerprint-auth/>
      In reply to: https://blog.rubenwardy.com/2022/11/16/thinkpad-x1-fingerprint-auth/
      </a><p>nice</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Who would have thought YAML was so bad]]></title>
        <id>https://www.tomoliver.net/notes/2023-03-03-2023</id>
        <link href="https://www.tomoliver.net/notes/2023-03-03-2023"/>
        <updated>2023-03-03T20:24:46.000Z</updated>
        <summary type="html"><![CDATA[Who would have thought YAML was so bad]]></summary>
        <content type="html"><![CDATA[<a class="u-in-reply-to" href=https://blog.rubenwardy.com/2023/02/17/interesting-reads/>
      In reply to: https://blog.rubenwardy.com/2023/02/17/interesting-reads/
      </a><p>Who would have thought YAML was so bad</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[This was super helpful for implementing webmentions on my si...]]></title>
        <id>https://www.tomoliver.net/notes/2023-03-03-1547</id>
        <link href="https://www.tomoliver.net/notes/2023-03-03-1547"/>
        <updated>2023-03-03T17:54:48.000Z</updated>
        <summary type="html"><![CDATA[This was super helpful for implementing webmentions on my site! Thanks!]]></summary>
        <content type="html"><![CDATA[<a class="u-in-reply-to" href=https://bionicjulia.com/blog/integrating-webmentions-nextjs-blog>
      In reply to: https://bionicjulia.com/blog/integrating-webmentions-nextjs-blog
      </a><p>This was super helpful for implementing webmentions on my site! Thanks!</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Am also thinking about upgrading...]]></title>
        <id>https://www.tomoliver.net/notes/2023-03-03-1443</id>
        <link href="https://www.tomoliver.net/notes/2023-03-03-1443"/>
        <updated>2023-03-03T18:11:34.000Z</updated>
        <summary type="html"><![CDATA[Am also thinking about upgrading...]]></summary>
        <content type="html"><![CDATA[<a class="u-in-reply-to" href=https://bionicjulia.com/blog/upgrading-from-nextjs-v12-to-v13>
      In reply to: https://bionicjulia.com/blog/upgrading-from-nextjs-v12-to-v13
      </a><p>Am also thinking about upgrading...</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Oh, looks like I have to paste the link explicitly for it to...]]></title>
        <id>https://www.tomoliver.net/notes/2023-02-12-2052</id>
        <link href="https://www.tomoliver.net/notes/2023-02-12-2052"/>
        <updated>2023-03-03T15:07:54.000Z</updated>
        <summary type="html"><![CDATA[Oh, looks like I have to paste the link explicitly for it to work.
Anyways, here it is:
https://blog.rubenwardy.com/2022/03/17/plant-monitor/]]></summary>
        <content type="html"><![CDATA[<a class="u-in-reply-to" href=https://blog.rubenwardy.com/webmention_test/>
      In reply to: https://blog.rubenwardy.com/webmention_test/
      </a><p>Oh, looks like I have to paste the link explicitly for it to work.</p>
<p>Anyways, here it is:</p>
<p><a href="https://blog.rubenwardy.com/2022/03/17/plant-monitor/">https://blog.rubenwardy.com/2022/03/17/plant-monitor/</a></p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Not as cool as this post right here am I rite m8s??? 🌳💻🎉]]></title>
        <id>https://www.tomoliver.net/notes/2023-02-12-2038</id>
        <link href="https://www.tomoliver.net/notes/2023-02-12-2038"/>
        <updated>2023-03-03T15:07:54.000Z</updated>
        <summary type="html"><![CDATA[Not as cool as this post right here am I rite m8s??? 🌳💻🎉]]></summary>
        <content type="html"><![CDATA[<a class="u-in-reply-to" href=https://blog.rubenwardy.com/webmention_test/>
      In reply to: https://blog.rubenwardy.com/webmention_test/
      </a><p>Not as cool as <a href="https://blog.rubenwardy.com/2022/03/17/plant-monitor/">this post</a> right here am I rite m8s??? 🌳💻🎉</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Nested reply perhaps?]]></title>
        <id>https://www.tomoliver.net/notes/2023-02-12-1940</id>
        <link href="https://www.tomoliver.net/notes/2023-02-12-1940"/>
        <updated>2023-03-03T15:07:54.000Z</updated>
        <summary type="html"><![CDATA[Nested reply perhaps?]]></summary>
        <content type="html"><![CDATA[<a class="u-in-reply-to" href=https://www.tomoliver.net/notes/2023-02-10-1740>
      In reply to: https://www.tomoliver.net/notes/2023-02-10-1740
      </a><p>Nested reply perhaps?</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[This should be a reply???]]></title>
        <id>https://www.tomoliver.net/notes/2023-02-10-1740</id>
        <link href="https://www.tomoliver.net/notes/2023-02-10-1740"/>
        <updated>2023-03-03T15:07:54.000Z</updated>
        <summary type="html"><![CDATA[This should be a reply???]]></summary>
        <content type="html"><![CDATA[<a class="u-in-reply-to" href=https://www.tomoliver.net/notes/2023-02-06-1951>
      In reply to: https://www.tomoliver.net/notes/2023-02-06-1951
      </a><p>This should be a reply???</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[So this is what shouting into the void feels like...
Hello, ...]]></title>
        <id>https://www.tomoliver.net/notes/2023-02-06-1951</id>
        <link href="https://www.tomoliver.net/notes/2023-02-06-1951"/>
        <updated>2023-11-20T15:40:50.000Z</updated>
        <summary type="html"><![CDATA[So this is what shouting into the void feels like...
Hello, this is my first "note".
Probably not going to write too many until I'm sure I have web mentions all working.]]></summary>
        <content type="html"><![CDATA[<p>So this is what shouting into the void feels like...</p>
<p>Hello, this is my first &quot;note&quot;.</p>
<p>Probably not going to write too many until I&#x27;m sure I have web mentions all working.</p>]]></content>
        <author>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </author>
        <contributor>
            <name>Tom Oliver</name>
            <email>me@tomoliver.net</email>
        </contributor>
    </entry>
</feed>