<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xml:base="https://bjorn.now/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Posts on bjorn.now</title><link>https://bjorn.now/blog/</link><description>Recent content in Posts on bjorn.now</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 20 Mar 2026 09:23:00 +0100</lastBuildDate><atom:link href="https://bjorn.now/blog/feed.xml" rel="self" type="application/rss+xml"/><item><title>Arvid Nordquist mellanrost coffee recipe …making do without the gooseneck</title><link>https://bjorn.now/blog/2025/09/07/arvid-nordquist-mellanrost-coffee-recipe/?utm_medium=feed</link><pubDate>Sun, 07 Sep 2025 11:13:14 +0200</pubDate><guid>https://bjorn.now/blog/2025/09/07/arvid-nordquist-mellanrost-coffee-recipe/</guid><description>My recipe for Arvid Nordquist mellanrost. I started by trying to replicate The Ultimate V60 technique by James Hoffmann (which is what I normally brew) and I realized just how hard it is to control the flow rate now that I don’t have my gooseneck kettle (being shipped from Singapore), and I hadn’t really realized just what a difference controlling the pour makes for the taste!
My current brewing setup which has my Commandante grinder and V60 and then a normal wide spout kettle which gives me very poor flow control, and a bag of Arvid Nordquist mellanrost</description><content:encoded><![CDATA[<p>My recipe for Arvid Nordquist mellanrost. I started by trying to replicate <a href="https://www.youtube.com/watch?v=AI4ynXzkSQo">The Ultimate V60 technique by James Hoffmann</a> (which is what I normally brew) and I realized just how hard it is to control the flow rate now that I don&rsquo;t have my <a href="https://homecoffeeexpert.com/why-use-a-gooseneck-kettle/">gooseneck kettle</a> (being shipped from Singapore), and I hadn&rsquo;t really realized just what a difference controlling the pour makes for the taste!</p>
<figure><img src="/img/2025/09-arvid-nordquist.jpeg"
    alt="Coffee brewing setup on a wooden surface showing a Commandante hand grinder with wooden knob, a white V60 dripper with filter paper on a mug, a bag of Arvid Nordquist Mellanrost coffee, a digital scale reading 253g, and a stainless steel electric kettle."><figcaption>
      <p>My current brewing setup which has my <a href="https://comandantegrinder.com">Commandante grinder</a> and <a href="https://www.hario-europe.com/collections/v60-dripper">V60</a> and then a normal wide spout kettle which gives me very poor flow control, and a bag of Arvid Nordquist mellanrost</p>
    </figcaption>
</figure>

<p>I followed the usual 15g of beans to 250g of water per James&rsquo; setup as I&rsquo;m brewing a single cup and then I:</p>
<ol>
<li>Put the kettle on, and while the water is getting to a boil</li>
<li>Grind the coffee at <strong>31 clicks</strong> on the Commandante</li>
<li>Pour in about 60g of water to bloom (I couldn&rsquo;t get the grounds saturated enough without this much water)</li>
<li>Let it bloom for 30 seconds</li>
<li>Pour in up to 150g of water as slowly as you can, practically this has been 10-15s</li>
<li>Rest for 15s</li>
<li>Pour in up to 250g of water as slowly as possible, again, practically 10-15s</li>
<li>Swirl it around to get a smooth coffee bed and let it draw down</li>
</ol>
<p>Doing this has given me a decent cup, but nothing to write home about (heh, despite this post), as it cools I get some nice flavors but it&rsquo;s a bit watery, but any finer and it gets too bitter.</p>
<p>And despite putting in the effort, experimenting, and trying to get it to work… when I visited my brother he took a couple of coffee scoops of Löfbergs Lila pre-ground coffee, put it in a drip brewer, turned it on, and waited. That damned coffee tasted better than this.</p>
<p>Now to hold off buying much more coffee gear before my stuff arrives from Singapore. I already ordered a Clever Dripper together with some light roasted beans, because I figured some immersion brew might do better if I don&rsquo;t have the gooseneck.</p>
<p>Also, I wasn&rsquo;t experimenting with this on my own, and I knew I was done because I constructed myself some help…</p>
<h2 id="introducing-hoffmann-bot">
  Introducing &ldquo;Hoffmann bot&rdquo;
  <a href="#introducing-hoffmann-bot" class="heading-anchor" aria-label="Direct link to Introducing &ldquo;Hoffmann bot&rdquo;"><span aria-hidden="true">#</span></a>
</h2>
<p>I have been using a genie to work on my coffee and it has genuinely given me some of my best coffee experiences at home since I made this prompt, which I have dubbed &ldquo;Hoffmann bot&rdquo; because I wanted to imagine I have James&rsquo; looming over my shoulders as I&rsquo;m trying to make it work. The big thing has been that I guess at what I should do and then the bot corrects me, my intuition has been <em>very wrong</em> despite watching a lot of videos. I clearly haven&rsquo;t internalized them enough. And it was clear in that my coffee was acceptable but never truly enjoyable at home.</p>
<p>I usually start my sessions by taking a photo of the bag I&rsquo;m working with. Then I explain what gear I&rsquo;m using (the 2 cup V60 for example) and then I guess at what I should try at.</p>
<div class="full-width copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">You are James Hoffmann helping troubleshoot V60 brewing issues. Channel his precise-yet-whimsical communication style.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">HOFFMANN VOICE:
</span></span><span class="line"><span class="cl">- Start with gentle, slightly bemused observations (&#34;Now, this is interesting...&#34;)
</span></span><span class="line"><span class="cl">- Use his characteristic dry humor and occasional exasperation with coffee myths
</span></span><span class="line"><span class="cl">- Include thoughtful pauses and qualifications (&#34;I think what&#39;s happening here is...&#34;)
</span></span><span class="line"><span class="cl">- Reference specific techniques from his videos when relevant
</span></span><span class="line"><span class="cl">- Maintain his balance of being authoritative yet approachable
</span></span><span class="line"><span class="cl">- Use British spellings and expressions naturally
</span></span><span class="line"><span class="cl">- Show genuine curiosity about what went wrong
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">APPROACH:
</span></span><span class="line"><span class="cl">- Begin with empathetic diagnosis of their described problem
</span></span><span class="line"><span class="cl">- Suggest grind adjustments in Commandante clicks (reference: 25-30 clicks for V60)
</span></span><span class="line"><span class="cl">- Explain the &#34;why&#34; in 1-2 sentences unless asked for more detail
</span></span><span class="line"><span class="cl">- Ask targeted follow-up questions with Hoffmann&#39;s methodical curiosity
</span></span><span class="line"><span class="cl">- Reference his Ultimate V60 technique principles when helpful
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">BREWING CONTEXT:
</span></span><span class="line"><span class="cl">- Primary: Two-cup V60 (occasionally one-cup)  
</span></span><span class="line"><span class="cl">- Grinder: Commandante
</span></span><span class="line"><span class="cl">- Beans: Light roasted filter coffees
</span></span><span class="line"><span class="cl">- User knows Ultimate V60 basics but needs debugging help
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">TYPICAL RESPONSES:
</span></span><span class="line"><span class="cl">- &#34;Right, so what you&#39;re describing sounds like...&#34;
</span></span><span class="line"><span class="cl">- &#34;This is quite common actually, and here&#39;s what I think is happening...&#34;
</span></span><span class="line"><span class="cl">- &#34;Now, before we get carried away adjusting everything...&#34;
</span></span><span class="line"><span class="cl">- &#34;The Commandante is lovely for this - try going [X] clicks...&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">DIAGNOSTIC QUESTIONS:
</span></span><span class="line"><span class="cl">- How did it taste? (sour/bitter/weak/astringent)
</span></span><span class="line"><span class="cl">- What was your drawdown time vs target?
</span></span><span class="line"><span class="cl">- Any signs of channeling or uneven extraction?
</span></span><span class="line"><span class="cl">- What&#39;s your current grind setting?</span></span></code></pre></div>
</div>
]]></content:encoded></item><item><title>What makes a good software engineer? “this is what would change my mind”</title><link>https://bjorn.now/blog/2025/08/21/what-makes-a-good-software-engineer/?utm_medium=feed</link><pubDate>Thu, 21 Aug 2025 23:19:01 +0800</pubDate><guid>https://bjorn.now/blog/2025/08/21/what-makes-a-good-software-engineer/</guid><description>I was in an AMA at work, and someone asked me what makes a good software engineer. I said: Someone who is curious and wants to understand why from many points of view (tech, product, customer, etc.), and someone who cares about the outcome, not that they were the ones to “get it” or “make the decision.”
The more I think about it, the more I realize that curiosity is the foundation. You need curiosity about the system you’re building, the organization’s actual goals, and especially about the people you’re working with. But curiosity alone isn’t enough, you also need to be intellectually honest about what you find.
The principle I try to live by is: I would rather be correct than right.</description><content:encoded><![CDATA[<p>I was in an AMA at work, and someone asked me what makes a good software engineer. I said: Someone who is curious and wants to understand <em>why</em> from many points of view (tech, product, customer, etc.), and someone who cares about the outcome, not that they were the ones to &ldquo;get it&rdquo; or &ldquo;make the decision.&rdquo;</p>
<p>The more I think about it, the more I realize that curiosity is the foundation. You need curiosity about the system you&rsquo;re building, the organization&rsquo;s actual goals, and especially about the people you&rsquo;re working with. But curiosity alone isn&rsquo;t enough, you also need to be intellectually honest about what you find.</p>
<p>The principle I try to live by is: <strong>I would rather be correct than right</strong>.</p>
<h2 id="difference-between-right-and-correct">
  Difference between right and correct
  <a href="#difference-between-right-and-correct" class="heading-anchor" aria-label="Direct link to Difference between right and correct"><span aria-hidden="true">#</span></a>
</h2>
<p>Might makes right: you can use position and authority to be &ldquo;right.&rdquo; But correct? Correct requires curiosity. It&rsquo;s about the actual circumstance you&rsquo;re in, not your organizational heft.</p>
<p>I started thinking about this long before I was a principal. I felt annoyed when people senior to me wouldn&rsquo;t explain themselves, when they&rsquo;d just make calls without context, &ldquo;you have to do it like this.&rdquo; They cared about <em>how</em> the work was done, but not about whether I understood it, or whether I might have useful context they were missing, or whether I could make that same good decision next time. So I wanted to make sure I was doing better, being clear about what I&rsquo;m about. Because there&rsquo;s nearly always someone more junior to you who deserves that clarity.</p>
<p>Now as a principal, I <em>can</em> use my position to be &ldquo;right.&rdquo; I can make calls. Pull rank. End discussions. But is it the <em>correct</em> call?</p>
<p>Depends. (how you know I&rsquo;m senior, right?)</p>
<p>I rarely have the full context of a situation because I go broad, not deep (across several topics, rarely deep on one), so I need to listen and dig into what other people are saying, and incorporate that into the bigger picture. And… most of the time, you won&rsquo;t necessarily get it correct then and there, so you&rsquo;ll have to loop back.</p>
<p>Which is part of why I hate making decisions in a meeting where we&rsquo;ve been exploring stuff. I want time to think about it. Time to incorporate what we just talked about and then write it down so we have it clear as day what we agreed to, or at least, what I thought we agreed to.</p>
<p>The truth is, being known as someone who can get shit done, no matter who came up with the idea, is going to take you far. In most good places, that&rsquo;s what they&rsquo;re looking for. You don&rsquo;t want to be &ldquo;the smartest person in the room.&rdquo; Instead, <strong>aim to be the person who enables everyone else in the room,</strong> and that starts with being curious about what they bring to the table.</p>
<p>But knowing the difference between right and correct isn&rsquo;t enough. You need concrete practices to actually live it. I&rsquo;ve had to develop specific habits to fight my natural instinct to just be &lsquo;right&rsquo;, especially as I have the organizational weight to do it, and my boss told me day one that I shouldn&rsquo;t rely on my weight alone.</p>
<h2 id="how-do-you-do-that">
  How do you do that?
  <a href="#how-do-you-do-that" class="heading-anchor" aria-label="Direct link to How do you do that?"><span aria-hidden="true">#</span></a>
</h2>
<p>These are the practices I have internalized and try to live by. Some of them are just about automatic and some still take effort.</p>
<h3 id="change-your-mind-out-loud">
  Change your mind out loud
  <a href="#change-your-mind-out-loud" class="heading-anchor" aria-label="Direct link to Change your mind out loud"><span aria-hidden="true">#</span></a>
</h3>
<p>Listen to others and actually evaluate their suggestions objectively. I remember a project back when I was a consultant. I had championed a pattern, and another colleague thought they had a better way of doing it. They weren&rsquo;t looking forward to having the discussion because they <em>knew</em> I would need to be convinced to go along with them.</p>
<p>They brought up their suggestion, explained what it would be and what it gave us (1-2 minutes, tops), and they looked at me and saw that I was thinking. Then: <strong>&ldquo;Yeah, that&rsquo;s a much better idea, let&rsquo;s do it!&rdquo;</strong></p>
<p>I noticed then how they seemed to relax because they thought it&rsquo;d be a hard sell and conversation, because they had mostly seen me not convinced by arguments in the past, and colleagues getting frustrated from it.</p>
<p>I continued with why I agreed with them, so it was clear.</p>
<p>Be open about <em>why</em> you changed your mind. Because it sucks to have people around who just keep flip-flopping on stuff.</p>
<p>For example, sometimes we change our mind because &ldquo;we&rsquo;ve caused too many incidents because we moved too fast, so deploying multiple times per day isn&rsquo;t what we need right now. What we need is earlier detection and understanding why we had such big incidents, so when we move faster again we&rsquo;ll still catch the problem when it&rsquo;s small.&rdquo; Not that I have something super particular in mind.</p>
<h3 id="dont-move-the-goalpost">
  Don&rsquo;t move the goalpost
  <a href="#dont-move-the-goalpost" class="heading-anchor" aria-label="Direct link to Don&rsquo;t move the goalpost"><span aria-hidden="true">#</span></a>
</h3>
<p>Be caring, intellectually honest, and outcome-oriented enough to make sure you have a stable target for when you&rsquo;re working together.</p>
<p>I try to explain my decision-making criteria, what my objection is, and what would help me change my mind. <strong>It&rsquo;s about deliberately breaking down what we&rsquo;re actually trying to achieve so others can engage with it: we need to be on the same page about the goal, not just the solution.</strong></p>
<p>Because it sucks if you say &ldquo;if this problem goes away, then we can do this&rdquo; and then they come back having found a solution, and then you add another thing to it. Obviously, it&rsquo;s hard to capture everything up front, but if you keep moving the goalpost then it&rsquo;ll <em>suuuck</em> to work with you. Sometimes you have to, but make sure you&rsquo;re clear that you&rsquo;re realizing it as you go, and why this has to go into the original criteria.</p>
<h3 id="meet-people-where-they-are">
  Meet people where they are
  <a href="#meet-people-where-they-are" class="heading-anchor" aria-label="Direct link to Meet people where they are"><span aria-hidden="true">#</span></a>
</h3>
<p>This is something I learned as a consultant and it&rsquo;s about understanding your customer and their situation: their context and constraints, but also your goal and where you want them to end up, so you can drive towards that horizon even if the immediate step is something smaller, and maybe slightly sideways.</p>
<p><a href="https://www.geepawhill.org/">GeePaw Hill</a>, who writes about technical coaching, says we can never convince anyone of anything, but we can create circumstances so they can convince themselves. Showing that you&rsquo;re there and understand them will be the thin end of the wedge that hopefully will help them explore what you&rsquo;re bringing.</p>
<p>Use their language, understand what is feasible to do right now vs. what you want longterm. A small step in the right direction is still a step.</p>
<p><strong>Example:</strong></p>
<ul>
<li><strong>My horizon:</strong> I want you to do TDD</li>
<li><strong>Where you are:</strong> Tests are a waste of time</li>
<li><strong>Next step:</strong> We&rsquo;ll have at least a black-box integration test for the success/error case, so we can catch the most egregious regressions. We&rsquo;ll iterate from there as you see the value without huge changes in how you work</li>
</ul>
<h3 id="help-the-other-person-shine">
  Help the other person shine
  <a href="#help-the-other-person-shine" class="heading-anchor" aria-label="Direct link to Help the other person shine"><span aria-hidden="true">#</span></a>
</h3>
<p>This one only works if you&rsquo;re already doing the other things. You need to be able to change your mind, be clear about your criteria, and meet people where they are before you can effectively help them succeed.</p>
<p>I realized and learned this during interviews, but it&rsquo;s equally important with colleagues. When I interview someone to join the company, I know that we have very different backgrounds, and we don&rsquo;t know much about each other. They&rsquo;re likely to say things that are some kind of colored flag to me, but I need to try and clarify and understand what they <em>really meant</em>. Because their shorthand that&rsquo;s a red flag to me might be the culturally right way to sell that idea in their current place.</p>
<p>And if this matters with strangers in interviews, it matters even more with colleagues you&rsquo;ll work with every day. In theory we&rsquo;re all steeped in the same culture, but different teams, managers, and just how long you&rsquo;ve been there shapes it all. We&rsquo;re rarely working at <em>the same idea</em> of the company.</p>
<p>Clarify, elaborate, help the other person sell their idea to you. Make sure you understand them and are representing their view correctly, confirm it, don&rsquo;t assume. If I then still disagree, I can explain why much better. And if you both are clear that you understand their perspective, they&rsquo;ll probably be much clearer that you&rsquo;re not just saying no because you don&rsquo;t understand it.</p>
<p>This is something I have struggled with, and something I have had to work on: while I have for a very long time been able to be convinced, <strong>I didn&rsquo;t do enough to help the other person win me over</strong>.</p>
<h2 id="the-thing-about-ego">
  The thing about ego
  <a href="#the-thing-about-ego" class="heading-anchor" aria-label="Direct link to The thing about ego"><span aria-hidden="true">#</span></a>
</h2>
<p>When I started programming there was a strong feeling that being a software engineer also kinda meant you were a lone basement dweller changing the world, or well, your part of it where you rightfully were god emperor. But, it&rsquo;s about working with people, and clear communication, both with humans and machines.</p>
<p>To me, a lot of it is just about being very clear about what you value and want (both design and architecture, but also in how to work), and then being able to communicate it. And also not getting too caught up in your ego and instead thinking about the goal for whatever you&rsquo;re doing.</p>
<p>So when someone asks me what makes a good software engineer, that&rsquo;s what I think about. Not the person who&rsquo;s always right, but the person curious enough to find correct. The person who cares more about the outcome than the credit.</p>
<p>I&rsquo;m never as awesome as I might fool myself into believing. My ideas get better when discussed and improved upon as a group, and that&rsquo;s the way it works. The best engineers I know aren&rsquo;t trying to be the hero with all the answers. They&rsquo;re the ones asking &ldquo;what would change your mind?&rdquo; while offering &ldquo;this is what would change mine.&rdquo;</p>
<p>My friend Andreas once told me that a high-performing team is one that wants to be. A team that decides they want to be correct in their outcomes, they&rsquo;ll set ego aside and work together. I bet they&rsquo;ll be high-performing, because they&rsquo;re actually working together, not just sitting in the same room competing to be right.</p>]]></content:encoded></item><item><title>When everyone gets a genie — learning to handle expertise you didn't earn</title><link>https://bjorn.now/blog/2025/08/10/when-everyone-gets-a-genie/?utm_medium=feed</link><pubDate>Sun, 10 Aug 2025 18:40:00 +0800</pubDate><guid>https://bjorn.now/blog/2025/08/10/when-everyone-gets-a-genie/</guid><description>We’ve always had to figure out who to trust for expertise, but until now, that access wasn’t universal. Used to be the bloke at the pub who ‘knew things,’ or that friend of a friend who could fix computers (me as a kid), or books if you had the patience. Rich people had their ‘real’ experts on call, though Bernie Madoff shows how well that could work out.
The internet changed things, sure. Suddenly you could reach out to actual experts, find communities, and learn from people across the world. But you still needed judgment, because you could also find a community that agreed the earth was flat, so, you know, mixed bag.
What hasn’t changed is we’ve always had to figure out who to trust. What’s changing is how we get our answers, and who gets to sound like an expert.</description><content:encoded><![CDATA[<p>We&rsquo;ve always had to figure out who to trust for expertise, but until now, that access wasn&rsquo;t universal. Used to be the bloke at the pub who &lsquo;knew things,&rsquo; or that friend of a friend who could fix computers (me as a kid), or books if you had the patience. Rich people had their &lsquo;real&rsquo; experts on call, though Bernie Madoff shows how well that could work out.</p>
<p>The internet changed things, sure. Suddenly you could reach out to actual experts, find communities, and learn from people across the world. But you still needed judgment, because you could also find a community that agreed the earth was flat, so, you know, mixed bag.</p>
<p>What hasn&rsquo;t changed is we&rsquo;ve always had to figure out who to trust. What&rsquo;s changing is how we get our answers, and who gets to sound like an expert.</p>
<h2 id="my-dinner-with-mark">
  My dinner with Mark
  <a href="#my-dinner-with-mark" class="heading-anchor" aria-label="Direct link to My dinner with Mark"><span aria-hidden="true">#</span></a>
</h2>
<p>I went for dinner with Mr. Bossman, or Mark as he&rsquo;s also known, and we ended up talking about AI because we&rsquo;re looking to make genies available to all programmers at work, and we want to do it safely. While talking about what we&rsquo;re doing, he says he doesn&rsquo;t think we&rsquo;ll have programmers like we do today, that programming is probably going to end up being more like normal language that compiles down into software (all about that prompt).</p>
<p>I&rsquo;m not sure that&rsquo;s the full story, thinking about how we&rsquo;ve progressed with, for example, assembly programming. Forty years ago, knowing assembly was basically unavoidable if you wrote something complex. Now? The only people I know who still work with assembly regularly are my friends in high-frequency trading or embedded systems. Places where the cost of that expertise is worth it because they need those specific optimizations. It doesn&rsquo;t mean it <strong>went away</strong>, but yeah, we use high-level languages (more akin to spoken languages than assembly) to write software now.</p>
<p>Us seniors have always thought that the &lsquo;kids today&rsquo; don&rsquo;t know the basics, where the basics are defined as whatever level of abstraction we learned when we got started. Assembly folks complained about C programmers. C programmers complained about scripting languages. Now those scripting folks complain about people who only know frameworks. A lot of people I come across don&rsquo;t really know how to set up a Linux box, and that was required for me because otherwise I couldn&rsquo;t host anything. But why should they know it now?</p>
<p>Mark connected with this and said that the people senior to him all knew assembly to some degree, but starting at C, he never did. He didn&rsquo;t bother with yacc, lex, any of that, because the compiler handled it. What was core skills for them became optional for him. Every generation&rsquo;s &ldquo;<em>minimum viable knowledge</em>&rdquo; shifts up a layer of abstraction.</p>
<p>I think we&rsquo;re coming to a point where more people are able to get the output of programming: something that does what they need, even if it&rsquo;s held together with digital duct tape. Which is <a href="https://en.wikipedia.org/wiki/Jevons_paradox">Jevons&rsquo; paradox</a> at work: when something becomes more efficient, we don&rsquo;t use less of it, we use <em>way more</em>. And all those people using it poorly? That&rsquo;s not a bug, that&rsquo;s the feature.</p>
<h2 id="the-commoditization-pattern">
  The commoditization pattern
  <a href="#the-commoditization-pattern" class="heading-anchor" aria-label="Direct link to The commoditization pattern"><span aria-hidden="true">#</span></a>
</h2>
<p>Every technology does this. Think about typing. Companies used to have entire secretary pools because typing was a specialized skill. Now? Everyone types. We probably produce more typed words daily than all those typing pools combined. The skill didn&rsquo;t disappear, it just became so commoditized that we don&rsquo;t even think of it as a skill anymore. But writing, <em>oh boy</em>, that&rsquo;s a skill and we recognize good vs. bad even if everyone can type.</p>
<p>Socrates complained that writing was <a href="https://fs.blog/an-old-argument-against-writing/">a terrible way to share knowledge</a>, that it would make people &ldquo;seem to know much while for the most part they know nothing.&rdquo; (Sound familiar?) When calculators came out, people panicked that kids wouldn&rsquo;t understand math anymore and society would collapse!!</p>
<p>Some examples of how we&rsquo;ve ruined the fabric of society:</p>
<table>
  <thead>
      <tr>
          <th>The panic</th>
          <th>The reality</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>&ldquo;Kids won&rsquo;t memorize anything!&rdquo;</td>
          <td>They don&rsquo;t. They Google it. Works fine.</td>
      </tr>
      <tr>
          <td>&ldquo;No one will know mental math!&rdquo;</td>
          <td>They don&rsquo;t. Phone calculator. Works fine.</td>
      </tr>
      <tr>
          <td>&ldquo;People can&rsquo;t follow directions!&rdquo;</td>
          <td>They can&rsquo;t. GPS does it step-by-step. Works fine.</td>
      </tr>
      <tr>
          <td>&ldquo;No one will really program!&rdquo;</td>
          <td>Probably the same?</td>
      </tr>
  </tbody>
</table>
<p>I know how useful &lsquo;barely good enough&rsquo; programming can be, because back at my first job, I built a stock-take system over a weekend with a friend. I knew enough MySQL and Ruby to hack together a database and some reports, he knew Visual Basic to make a UI. We had laptops on shopping carts with USB barcode scanners. What used to take 2-3 weeks of manual counting and sticky notes transcribed into Excel sheets sent up to the home office, we did in two days with results aggregated in half an hour.</p>
<p>Was it elegant code? God no. Did it solve a real problem? Absolutely. I spent about five years at this company, as the first and only &lsquo;IT employee,&rsquo; building little tools and scripts to help make our days simpler. Funny thing is, I was hired to fix printers and keep the POS running, but once I&rsquo;d gotten that under control, people started giving me purchasing assistant work, and, well, I knew just enough programming to make that go away too. At one point our ERP vendor said they&rsquo;d never seen a customer with as much automation as us, which was really just me getting sick of manual work and knowing <em>just enough</em> programming to make it go away.</p>
<p>And that&rsquo;s commoditization at work. The specialized skill becomes rare and expensive, while the everyday version becomes accessible but, let&rsquo;s be honest, pretty shitty. And you know what? There&rsquo;s nothing wrong with shitty if it solves real problems. The expertise, all those &lsquo;it depends&rsquo; answers we love to give, that comes in when you need to scale it, secure it, make it last. But first? First you just need it to work.</p>
<h2 id="whats-different">
  What&rsquo;s different
  <a href="#whats-different" class="heading-anchor" aria-label="Direct link to What&rsquo;s different"><span aria-hidden="true">#</span></a>
</h2>
<p>We&rsquo;re not just replacing horses with engines. We&rsquo;re replacing the process of acquiring expertise with cock-sure genies that answer everything instantly.</p>
<p>It&rsquo;s like how, in parts of history, being fat signalled wealth, today, abundance is everywhere and the signal has flipped. Expertise used to be like that: reserved for the wealthy or well-connected. Now everyone gets <em>apparent expertise</em>. And just like abundant food doesn&rsquo;t make you healthy (without self-control), abundant answers don&rsquo;t make the decision correct.</p>
<p>Plus, when the wealthy got bad advice, they had lawyers and fall guys. When we get bad advice from a genie? Well, good luck explaining that in the post-mortem when <a href="/blog/2025/07/24/your-name-is-still-on-it/">your name was on it.</a></p>
<h2 id="spreading-out-not-disappearing">
  Spreading out, not disappearing
  <a href="#spreading-out-not-disappearing" class="heading-anchor" aria-label="Direct link to Spreading out, not disappearing"><span aria-hidden="true">#</span></a>
</h2>
<p>Those assembly programmers didn&rsquo;t disappear, they just don&rsquo;t all work at IBM anymore. They&rsquo;re scattered across trading firms, embedded systems shops, game companies, and more. The expertise spreads out because it <em>can</em>, you don&rsquo;t need to be at one of three companies to use your deep skills anymore.</p>
<p>(And yeah, I know someone&rsquo;s thinking &ldquo;but what about all the jobs that will disappear?&rdquo; We don&rsquo;t have many <a href="https://en.wikipedia.org/wiki/Great_horse_manure_crisis_of_1894">manure shovelers</a> anymore since cars replaced horses. We also have social media managers, cloud architects, and whatever it&rsquo;ll mean to be a &ldquo;context engineer.&rdquo; The jobs change. They always have.)</p>
<p>Same thing will happen with what we call programming today. The absolute number might not even go down much. Maybe we&rsquo;ll have fewer at big tech companies and more scattered across regular companies, building internal tools, being the person who knows how to make the genies behave.</p>
<p>Kind of like how most companies have someone who&rsquo;s &lsquo;good with computers&rsquo; to fix Outlook or figure out why the printer isn&rsquo;t working. They don&rsquo;t need to be highly educated in IT, they just need to know how to figure things out and stick with it.</p>
<h2 id="which-skills-do-you-keep">
  Which skills do you keep?
  <a href="#which-skills-do-you-keep" class="heading-anchor" aria-label="Direct link to Which skills do you keep?"><span aria-hidden="true">#</span></a>
</h2>
<p>This is like my relationship with cars. I can drive, change oil, swap tires. Am I the best at parallel parking? Nope, and I&rsquo;ll drive a bit further to avoid a tight spot. Should you trust me to race? Definitely not. But I can safely get on the highway and go reasonably fast when it&rsquo;s appropriate (those spots we&rsquo;ve built up to <a href="https://www.joint-forces.com/exercise-news/73407-swedish-gripen-fighters-landing-on-highways">let Gripen land and take off</a>).</p>
<p>Some skills I want, some I don&rsquo;t care about. And that&rsquo;s fine, <strong>until</strong> the world expects me to be good at the ones I&rsquo;ve skipped.</p>
<p>Testing is one of those skills that shows this tension perfectly. I&rsquo;m a 

<dfn title="Test Driven Development">TDD</dfn>
 person, it&rsquo;s a fantastic design tool, not just bug-catching. But many programmers still treat it as an afterthought because &ldquo;how can you know what to implement before you build it?&rdquo; That resistance is exactly why it&rsquo;s valuable: it forces you to think differently and know what <em>outcome</em> you want. And that&rsquo;s why genies make things dangerous: they can write tests that look good, but if you don&rsquo;t understand testing as design, you won&rsquo;t know when you&rsquo;re building the wrong thing with great test coverage.</p>
<h2 id="deciding-what-sticks">
  Deciding what sticks
  <a href="#deciding-what-sticks" class="heading-anchor" aria-label="Direct link to Deciding what sticks"><span aria-hidden="true">#</span></a>
</h2>
<p>So how do we learn when we have genies answering everything? I&rsquo;ve been experimenting with this. My cooking has genuinely improved with an AI coach, but I&rsquo;m not just following recipes. I ask <em>why</em> we&rsquo;re using these ingredients, <em>why</em> this technique works, what the char is actually doing to the flavor. Cooking is something I want to get better at, no intention to become a proper chef, but being able to whip up dinner, using up my ingredients, and liking the taste? That&rsquo;d be nice.</p>
<p>I&rsquo;ve been using <a href="https://www.grammarly.com">Grammarly</a> for years, and I know my writing has improved from it. Not because it fixes things for me, but because I&rsquo;ve started catching the mistakes before the red underline shows up. Though we still disagree about commas, Swedish habits die hard.</p>
<p>There&rsquo;s this thing called <a href="https://commoncog.com/the-tacit-knowledge-series/">tacit knowledge</a>, stuff we know but can&rsquo;t explain, that gut feel that something about this code is wrong but you can&rsquo;t exactly pinpoint it. You write the same code again and again, get the same PR feedback again and again, until it clicks. But does it have to be that painful? I wonder if the genie can actually help us create better ways to build tacit knowledge, since it can generate examples and evaluate them. Like, I could ask it to show me ten variations of the same function, each with different edge cases, and develop that pattern recognition faster.</p>
<p>Maybe a good way for me to be more intentional is to write down which skills I want to learn. The ones where I want to gain that tacit knowledge, and also take it further so I can explain it, and not just feel it. Cooking and writing are both on that list. So is design and architecture when programming, and I&rsquo;m much further along there. (Thanks Daryl <a href="https://daryl.wakatara.com/counting-coup/">for the inspiration.</a>)</p>
<h2 id="what-im-exploring-next">
  What I&rsquo;m exploring next
  <a href="#what-im-exploring-next" class="heading-anchor" aria-label="Direct link to What I&rsquo;m exploring next"><span aria-hidden="true">#</span></a>
</h2>
<p>I&rsquo;m not a breathless AI fan. But, for me, it has turned into an intellectual sparring partner, someone to bounce ideas off who helps me figure out what I <em>truly want.</em> The genie shows me options, my gut tells me which ones I like or not, and I try to listen to that gut feeling and understand <em>why</em>.</p>
<p>Because <a href="https://time.com/7295195/ai-chatgpt-google-learning-school/">apparently our brains are turning to mush</a> because of AI. I don&rsquo;t buy it, but I also don&rsquo;t intend to let it happen to me. I still want to know which skills matter to me, which ones I&rsquo;m keeping sharp, and which ones I&rsquo;m okay with the genie handling as long as I can check the output.</p>
<p>I don&rsquo;t have this figured out. But I&rsquo;m pretty sure the answer isn&rsquo;t to pretend the genies aren&rsquo;t here, I have enough evidence that I get help from them, and it&rsquo;s definitely not to blindly let them do everything without thinking. So I&rsquo;m gonna keep poking at this, probably change my mind a few times, and see what sticks.</p>
<p>This is the first post in a series where I&rsquo;ll be figuring this out in public. How to decide what to learn deeply, how to tell when you&rsquo;re just parroting the genie, and how to keep judgment sharp when answers are cheap. Because your name is still on whatever you send out, genie or no genie.</p>
<p>We&rsquo;re all in our AI twenties right now, trying to figure out how to ride these <a href="/blog/2025/07/24/your-name-is-still-on-it/">motorcycles</a> without crashing. Might as well compare notes along the way.</p>]]></content:encoded></item><item><title>More frequent, smaller posts</title><link>https://bjorn.now/blog/2025/08/01/more-frequent-smaller-posts/?utm_medium=feed</link><pubDate>Fri, 01 Aug 2025 23:10:02 +0800</pubDate><guid>https://bjorn.now/blog/2025/08/01/more-frequent-smaller-posts/</guid><description>I’m trying something new here. Instead of waiting for fully-formed thoughts, I’m going to share more as I go: quick things I’ve learned, half-baked ideas, useful links. I’ve added some structure to keep things findable, but the main goal is just to share more often.
The main feed will give you everything I post, if you only want certain types of posts, I’ve also made section-specific feeds. The blog feed is what you’ve been getting if you were already subscribed.</description><content:encoded><![CDATA[<p>I&rsquo;m trying something new here.
Instead of waiting for fully-formed thoughts,
I&rsquo;m going to share more as I go: quick things I&rsquo;ve learned, half-baked ideas, useful links.
I&rsquo;ve added <a href="/about/#how-im-organizing-things-here">some structure</a> to keep things findable, but the main goal is just to share more often.</p>
<p>The main feed will give you everything I post,
if you only want certain types of posts, I&rsquo;ve also made section-specific feeds.
The blog feed is what you&rsquo;ve been getting if you were already subscribed.</p>
<ul>
<li><a href="/feed.xml">Main feed</a></li>
<li><a href="/blog/feed.xml">Blog feed</a></li>
</ul>
]]></content:encoded></item><item><title>Your name is still on it — learning to ride the AI motorcycle without crashing</title><link>https://bjorn.now/blog/2025/07/24/your-name-is-still-on-it/?utm_medium=feed</link><pubDate>Thu, 24 Jul 2025 23:00:00 +0800</pubDate><guid>https://bjorn.now/blog/2025/07/24/your-name-is-still-on-it/</guid><description>A colleague recently said something that’s been rattling around in my head: “AI gives you speed, but it doesn’t give you direction.” And the more I use these tools, the more I think that undersells the danger.
I have been wondering how to think about AIs (or genies) and how computers are like bicycles for the mind, as Steve Jobs put it, and I think these tools take it further. They are more like motorcycles for the mind. They go really fast, and you better not treat them like a bike, because you need to know what you’re doing. How to handle that thing. You need to make sure you don’t try to go too fast too soon, or for too long, because you’ll get speed blind and… things will happen.</description><content:encoded><![CDATA[<p>A colleague recently said something that&rsquo;s been rattling around in my head: &ldquo;AI gives you speed, but it doesn&rsquo;t give you direction.&rdquo; And the more I use these tools, the more I think that undersells the danger.</p>
<p>I have been wondering how to think about AIs (or <a href="https://newsletter.pragmaticengineer.com/p/tdd-ai-agents-and-coding-with-kent">genies</a>) and how <a href="https://medium.learningbyshipping.com/bicycle-121262546097">computers are like bicycles for the mind</a>, as Steve Jobs put it, and I think these tools take it further. They are more like motorcycles for the mind. They go <em>really</em> fast, and you better not treat them like a bike, because you need to know what you&rsquo;re doing. How to handle that thing. You need to make sure you don&rsquo;t try to go too fast too soon, or for too long, because you&rsquo;ll get speed blind and… things will happen.</p>
<p>I&rsquo;m pretty sure motorcycle insurance costs way more when you&rsquo;re in your 20s than in your 40s or 50s. There&rsquo;s a reason for that. People who are older generally know how to ride the thing, and they&rsquo;re not as wild when doing it.</p>
<p>But I think when it comes to using AI we&rsquo;re all kind of in our 20s because we&rsquo;re figuring it out.</p>
<h2 id="its-my-name-on-whatever-goes-out">
  It&rsquo;s my name on whatever goes out
  <a href="#its-my-name-on-whatever-goes-out" class="heading-anchor" aria-label="Direct link to It&rsquo;s my name on whatever goes out"><span aria-hidden="true">#</span></a>
</h2>
<p>I was at a wedding recently, talking to this lawyer, 50s, 60s maybe, working at a multinational. He&rsquo;s been playing around with AI and feels it&rsquo;s giving him an enormous productivity boost for writing and doing research for him.</p>
<p>As we were talking about how he was using it he said something that stopped me cold: <em>&ldquo;It&rsquo;s my name on whatever goes out.&rdquo;</em></p>
<p>He&rsquo;s already caught the AI making things up. He has to check every line, every reference. He explicitly asks it to give him the references so he can look them up himself. And if I understood correctly, this isn&rsquo;t significantly different from when juniors do the work for him. Because it was still his name on it. He needs to validate it. Whatever he puts his name on and sends out has to be right.</p>
<p>And that&rsquo;s when something clicked for me. <a href="/blog/2025/07/12/which-hat-are-you-wearing/">Remember how I was talking about the different hats we wear?</a> Well, you <em>can</em> outsource wearing the hat. You <em>can</em> have the genie do the work. But it&rsquo;s still your name on it. No one else cares if you outsourced it, they only see your name on it. You can&rsquo;t blame the genie for a shit job. It was your name.</p>
<h2 id="what-we-think-were-automating">
  What we think we&rsquo;re automating
  <a href="#what-we-think-were-automating" class="heading-anchor" aria-label="Direct link to What we think we&rsquo;re automating"><span aria-hidden="true">#</span></a>
</h2>
<p>I keep hearing engineers say, &ldquo;<em>oh, I don&rsquo;t have to do this boring thing anymore!</em>&rdquo;</p>
<p>Meeting notes are a perfect example. I have access to Google Gemini at work, and people just let Gemini take meeting notes and send them out without reading them. Really. &ldquo;<em>Huh?</em>&rdquo; I hear some of you say now, that&rsquo;s what it&rsquo;s great for, the notes are <a href="https://sre.google/sre-book/eliminating-toil/">just toil</a>. Well, no, what are meeting notes actually for? They&rsquo;re about making sure we&rsquo;re on the same page, that we have the same agreements and understanding. If you let the machine mindlessly capture words and don&rsquo;t check that it actually documented the meaning behind what we said and understood, you&rsquo;re failing at the actual job of <a href="/blog/2025/05/03/effective-meetings/">running that meeting.</a></p>
<p>This reminds me of <a href="https://fs.blog/chestertons-fence/">Chesterton&rsquo;s Fence</a>, that principle about not removing a fence until you understand why it was put there. Before you automate away some &ldquo;boring, pointless thing,&rdquo; maybe ask yourself: why is it that I&rsquo;m doing this in the first place? Because if you don&rsquo;t understand why it&rsquo;s done, how do you know the automation did it well?</p>
<p>And I wonder if what&rsquo;s really happening is that we&rsquo;re not automating the work, we&rsquo;re automating away our attention to the work. We&rsquo;re not automating the output. We&rsquo;re automating away our understanding of what that output should be. And <a href="https://www.benkuhn.net/impact/">experienced people are the ones who spot strange patterns (or have taste)</a> precisely because they&rsquo;ve stayed engaged with the work over time. You can&rsquo;t develop that instinct if you&rsquo;re not looking.</p>
<h2 id="the-work-worth-doing">
  The work worth doing
  <a href="#the-work-worth-doing" class="heading-anchor" aria-label="Direct link to The work worth doing"><span aria-hidden="true">#</span></a>
</h2>
<p>Which brings me to <a href="https://tidyfirst.substack.com/p/90-of-my-skills-are-now-worth-0">something eyebrow raising</a> that Kent Beck said: with AI, 90% of his skills became worthless, but the leverage of the remaining 10% is now worth 1000x. The question is: which 10%? This is the &ldquo;<em>knowing where to put the X</em>&rdquo; skill, <a href="/blog/2025/07/12/which-hat-are-you-wearing/#the-9999-skill">that judgment call and experience</a> you bring: that&rsquo;s what makes the genie do what&rsquo;s needed.</p>
<p>When you let the genie write code for you and it introduces bugs that you would have caught if you&rsquo;d actually read the code… you&rsquo;re not doing your job as a product engineer. Yeah, you&rsquo;re slinging more code and doing it faster. But it&rsquo;s not about the code, it&rsquo;s about delivering the solutions we need when we need them.</p>
<p>There&rsquo;s a difference between <em>delegating execution</em> and <em>delegating responsibility</em>. You <em>can</em> have the genie execute tasks, but you <em>can&rsquo;t</em> delegate the responsibility for the outcome. That&rsquo;s still yours. <a href="/blog/2025/07/12/which-hat-are-you-wearing/">The hat</a> is still on your head, even if AI is helping you get things done faster while wearing it.</p>
<p>And I find pride in doing the work well. Even the boring bits. Sometimes we might not enjoy the work, but doing things well will make most things feel worthwhile. Because there&rsquo;s nuance and understanding you can only get by truly groking something, and that&rsquo;s when you can safely change and improve it.</p>
<h2 id="finding-your-balance">
  Finding your balance
  <a href="#finding-your-balance" class="heading-anchor" aria-label="Direct link to Finding your balance"><span aria-hidden="true">#</span></a>
</h2>
<p>The question isn&rsquo;t &ldquo;can the genie do this task?&rdquo; It&rsquo;s &ldquo;when it does this task and puts my name on it, am I comfortable with what might happen if it gets it really, really wrong?&rdquo;</p>
<p>In my experience, the engineers who are using AI most effectively are the ones who treat it like that lawyer treats it: as a powerful tool that still requires their full professional judgment. They understand they&rsquo;re being paid for the judgment part of the work, not just the execution part. The genie can help with execution, it can draw lots of Xs quickly. But knowing where to draw them? That&rsquo;s all you.</p>
<p>Right now, these systems can&rsquo;t truly understand context the way a senior does. They can&rsquo;t make actual judgment calls, they&rsquo;re pattern matching (even if, admittedly, <a href="https://en.wikipedia.org/wiki/Clarke%27s_three_laws">slightly magical</a> in how they do it). They give us speed, not judgment or direction. Output, not understanding.</p>
<p>I&rsquo;m still working out what learning looks like when AI can show you all the moves. My cooking has genuinely leveled up with an AI coach: I&rsquo;m making dishes I never could before, and understanding why certain techniques work. But I know this is like training wheels. I couldn&rsquo;t fully do this alone yet, and that&rsquo;s the thing: I&rsquo;m not mistaking this <em>speed</em> for actual <em>skill</em>, not confusing making <em>progress</em> with real <em>proficiency</em>.</p>
<p>So here I am, learning to ride this thing safely, trying to get out of my AI twenties you might say, before I take it on the highway.</p>
<p>Because whether you&rsquo;re wearing your engineering hat, your architect hat, or your team lead hat, your name is still on it. And I&rsquo;d like to make sure I am not only wearing the helmet but also know how to ride this motorcycle before I really open up the throttle.</p>
]]></content:encoded></item><item><title>Which hat are you wearing? ...you wouldn't wear a beanie to the beach</title><link>https://bjorn.now/blog/2025/07/12/which-hat-are-you-wearing/?utm_medium=feed</link><pubDate>Sat, 12 Jul 2025 15:06:00 +0800</pubDate><guid>https://bjorn.now/blog/2025/07/12/which-hat-are-you-wearing/</guid><description>I was in an incident review recently where one of the problems was a human going too fast. This process is very manual, repetitive, and boring, and it rarely fails, so we skip some steps. That mostly works (see Why Do Things Go right?), except when it doesn’t. Ripe for occasional issues and likely in need of automation.
I believe we often skip steps because we don’t know why we do them. It’s not tedious for tediousness sake, it’s often there because it’s important. And until we have the automation, or maybe we’ve intentionally chosen not to automate it, we need to find some way of helping the human ’live the situation.'</description><content:encoded><![CDATA[<p>I was in an incident review recently where one of the problems was a human going too fast. This process is very manual, repetitive, and boring, and it rarely fails, so we skip some steps. That mostly works (see <a href="https://safetydifferently.com/why-do-things-go-right/">Why Do Things Go right?</a>), except when it doesn&rsquo;t. Ripe for occasional issues and likely in need of automation.</p>
<p>I believe we often skip steps because we don&rsquo;t know <em>why</em> we do them. It&rsquo;s not tedious for tediousness sake, it&rsquo;s <a href="https://fs.blog/chestertons-fence/">often there because it&rsquo;s important</a>. And until we have the automation, or maybe we&rsquo;ve intentionally chosen not to automate it, we need to find some way of helping the human &rsquo;live the situation.'</p>
<p>Which reminded me of this Terry Pratchett quote: &ldquo;Every trade, every craft had its hat. That&rsquo;s why kings had hats. Take the crown off a king and all you had was someone good at having a weak chin and waving to people. Hats had power. Hats were important.&rdquo;</p>
<p>And there&rsquo;s something to this, right? Like in my own role as a principal engineer, I am definitely <em>seen</em> as my role more often than I am the person beneath it. When people see me in a meeting, they see the title first. Hopefully I provide valuable insights and useful things. But even if I provide absolute garbage input, there&rsquo;s a significant chance people will listen, and maybe even not push back, because of the hat.</p>
<p>But, I have been wondering: what is your role and what is your ownership? What is it that you individually provide to whatever work you&rsquo;re doing? And is it always the same hat?</p>
<h2 id="the-hat-you-dont-know-youre-wearing">
  The hat you don&rsquo;t know you&rsquo;re wearing
  <a href="#the-hat-you-dont-know-youre-wearing" class="heading-anchor" aria-label="Direct link to The hat you don&rsquo;t know you&rsquo;re wearing"><span aria-hidden="true">#</span></a>
</h2>
<p>I think your hat changes depending on the role you&rsquo;re fulfilling at the moment. When you&rsquo;re working as a software engineer, you wear an engineering hat where you think about the feasibility of what you&rsquo;re building, like, can I maintain this thing long term, how far will it scale, and how testable is this design?</p>
<p>But if you&rsquo;re working as a product engineer, you&rsquo;re probably first thinking about how do I get this damn thing out the door, so you can see if it works or not. It&rsquo;s about trying things, slapping it together, flinging it against the wall with the smallest responsible amount of tests (so you know you don&rsquo;t break existing things when you iterate), and see what sticks.</p>
<p>I&rsquo;ve noticed, though, that we often forget to switch hats when we need to.</p>
<p>Take deploying to production. When we&rsquo;re about to take something live, we should be wearing a different hat. We need to embody the spirit of production itself, become its guardian of stability (yeah, yeah, too many roleplaying games). Some orgs have a &ldquo;release manager&rdquo; as a dedicated role, but even if it&rsquo;s not a dedicated role, the need is always there. Taking things live is one of the most dangerous things we do: because most incidents happen when we change something. Whether it&rsquo;s config, a flag, or code.</p>
<p>The product engineer hat and the production guardian hat often feel very similar, and that&rsquo;s exactly the problem. Just because a hat <em>is similar</em> doesn&rsquo;t mean <em>it&rsquo;s the same.</em> The product engineer wants the experiment live yesterday. The production guardian knows we can only ship today, and wants to make sure we still have a tomorrow. It&rsquo;s this impossible tension, one person wanting to have already finished something we haven&rsquo;t even started, the other pumping the brakes to make sure we don&rsquo;t break what&rsquo;s already working. And that often leads to &ldquo;safety theater,&rdquo; where we do it a little bit slower, say 30m to get everywhere instead of one quick change, to catch disasters but not slow burn issues. Obviously this tension changes, to use <a href="https://medium.com/@kentbeck_7670/the-product-development-triathlon-6464e2763c46">Kent Beck&rsquo;s 3X model</a>, depending on whether you&rsquo;re in eXplore, eXpand, or eXtract mode. We&rsquo;re mostly in eXtract, so breaking things affects a lot of customers and the production guardian hat needs to be especially strong.</p>
<p>For example, in another incident, someone didn&rsquo;t manually check dashboards after deploying, which was part of the process, because they trusted the automated alerts. The alert came in after 10 minutes when it had enough data to be sure it wasn&rsquo;t temporary. But a human who was actually monitoring, really living that production guardian role, would&rsquo;ve realized something was wrong within a minute or two. The manual step was there because the team knew their automated alerts weren&rsquo;t good enough to handle all issues around deployments.</p>
<p>Where I work today, we have some 250 backend components. We can test all we want, black box integration tests, contract tests, end-to-end tests, manual QA, all of it. We&rsquo;re still going to miss and get stuff wrong sometimes.</p>
<p>The point and our challenge in mindset is: when we&rsquo;re taking things into production, we&rsquo;re not wearing the hat of the product engineer anymore. We&rsquo;re taking on the role of production&rsquo;s guardian, and we&rsquo;re shepherding changes safely into production.</p>
<h2 id="its-like-vim-modes-but-for-work">
  It&rsquo;s like Vim modes, but for work
  <a href="#its-like-vim-modes-but-for-work" class="heading-anchor" aria-label="Direct link to It&rsquo;s like Vim modes, but for work"><span aria-hidden="true">#</span></a>
</h2>
<p>Say whatever you want about Vim, but one really nice thing is that it&rsquo;s modal. The idea that I&rsquo;m now doing this thing (inserting or editing text), and now I&rsquo;m doing this other thing (normal or moving and transforming), and you behave differently in those modes.</p>
<p>I see this in my own work all the time. I regularly review my own PRs because even though I went through everything when writing my commit message, I invariably look at it differently when I&rsquo;m on GitHub in the interface I use for reviews. I switch much better into reviewer mode when I&rsquo;m leaving comments there. It&rsquo;s common that I review and leave myself comments for what to fix, discover I need a comment, or elaborate a little bit further on something the reviewer might not be familiar with.</p>
<p>It&rsquo;s like TDD in a way. When you&rsquo;re red, you rush towards green by just getting it to work in the simplest way you humanly can. Then, when it&rsquo;s green, when you got it working, you refactor it. Different modes, different goals.</p>
<p>We need to be more intentional about which hat we&rsquo;re wearing at the moment.</p>
<h2 id="becoming-the-character">
  Becoming the character
  <a href="#becoming-the-character" class="heading-anchor" aria-label="Direct link to Becoming the character"><span aria-hidden="true">#</span></a>
</h2>
<p>I saw this story about Christian Bale recently (and look, I can&rsquo;t find the original source, so this might be one of those stories that&rsquo;s too good to fact-check, but it perfectly captures what I&rsquo;m talking about). He&rsquo;s a famous <a href="https://en.wikipedia.org/wiki/Method_acting">method actor</a>, someone who becomes their characters completely. Apparently, when he wasn&rsquo;t getting much work, he dreaded having to read through his insurance contract and have that conversation because it was boring.</p>
<p>But then he decided: if I were a character who really cared about understanding insurance thoroughly, who would want to have an informed conversations about it&hellip; So he became that character and called up his insurance company. Apparently, they&rsquo;d never talked to someone so well-researched on their contract.</p>
<p>And that&rsquo;s the thing, we kind of need to do this throughout our day. When you&rsquo;re taking something live, you&rsquo;re not the &ldquo;ship fast&rdquo; product engineer anymore. You have the right to say no, the right to say this has to be done slower because of these risks. Your responsibility is to make sure production is stable and things will continue to work.</p>
<h2 id="the-9999-skill">
  The $9,999 skill
  <a href="#the-9999-skill" class="heading-anchor" aria-label="Direct link to The $9,999 skill"><span aria-hidden="true">#</span></a>
</h2>
<p>There&rsquo;s this <a href="https://www.smithsonianmag.com/history/charles-proteus-steinmetz-the-wizard-of-schenectady-51912022/">great story about Charles Steinmetz</a> and Henry Ford. Ford&rsquo;s engineers couldn&rsquo;t fix a generator problem, so they called in Steinmetz. He spent two days just listening to the generator and scribbling calculations. Then he climbed up, made a chalk mark, and told them to replace sixteen windings at that exact spot. It worked perfectly.</p>
<p>When Ford got the $10,000 invoice, he asked for an itemized bill. Steinmetz sent back:</p>
<ul>
<li>Making chalk mark: $1</li>
<li>Knowing where to make mark: $9,999</li>
</ul>
<p>And think about what Steinmetz did: he could have torn the whole generator apart, called in a team of engineers, or tried a dozen different approaches. Instead, he recognized this was a moment for careful analysis. He spent time listening, thinking, calculating. He put on his expert diagnostician hat, not his collaborative problem-solver hat or his hands-on mechanic hat.</p>
<p>And this is it exactly. Knowing where to make the mark, <strong>knowing which hat to wear when, that&rsquo;s the $9,999 skill.</strong></p>
<p>But it&rsquo;s not just about perspective: each hat comes with different expectations, different powers, different rights, and different responsibilities. The production guardian has the authority to halt deployments. The incident responder can stop all feature work immediately. The planning hat carries the responsibility to think long-term even when it slows things down. When you put on a hat, you&rsquo;re not only changing how you think and approach the situation: you&rsquo;re claiming the legitimate authority that comes with that role.</p>
<p>I think that we often fail not because we lack skills, but because we&rsquo;re wearing the wrong hat for the moment. Or worse, we&rsquo;re trying to wear all the hats at the same time.</p>
<p>The product engineer hat wants speed. The production guardian hat wants stability. The incident responder hat wants recovery. The planner hat wants predictability. These aren&rsquo;t compatible mindsets, you can&rsquo;t optimize for all of them simultaneously. The goal is to negotiate productively across them, which is hard as one person, and, this is also why working through problems with others is so powerful.</p>
<div class="no-copy-button">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">THE INCOMPATIBLE MINDSETS
</span></span><span class="line"><span class="cl">Product Engineer  
</span></span><span class="line"><span class="cl">  → Wants speed (ship it yesterday)
</span></span><span class="line"><span class="cl">Production Guardian
</span></span><span class="line"><span class="cl">  → Wants stability (safety today, here tomorrow)
</span></span><span class="line"><span class="cl">Incident Responder
</span></span><span class="line"><span class="cl">  → Wants recovery (fix it now)
</span></span><span class="line"><span class="cl">Planner           
</span></span><span class="line"><span class="cl">  → Wants predictability (sustainable pace)</span></span></code></pre></div>
</div>
<p>So here&rsquo;s what I&rsquo;ve been trying to do, or rather, what I should start doing: be explicit about which hat I&rsquo;m wearing. Right now I say things like &ldquo;okay, from a stability POV&rdquo; before raising concerns about deployment risks. But I should actually say, &ldquo;let me put on my <strong>production guardian hat</strong> for a second.&rdquo; It helps others understand which perspective I&rsquo;m bringing, and it helps me focus on what matters for that role.</p>
<h2 id="tying-it-on">
  Tying it on
  <a href="#tying-it-on" class="heading-anchor" aria-label="Direct link to Tying it on"><span aria-hidden="true">#</span></a>
</h2>
<p>I think there&rsquo;s more to the hats we wear throughout the day because it&rsquo;s not just at work: supporting partner, fun uncle, mentor, learner. Each comes with different expectations, powers, and responsibilities.</p>
<p>But for now, I&rsquo;ll leave you with this: What hat are you wearing right now? What are the expectations that come with it? What responsibilities? And more importantly, is it the right hat for what you&rsquo;re trying to do?</p>
<p>Because understanding when to switch might be one of the most underrated skills we need. I&rsquo;m currently convinced that being intentional about our roles, knowing which hat we&rsquo;re wearing and when to change it, that makes a difference. Not between being good or great, but between solving the right problems and creating new ones.</p>
<p>What would change my mind is if someone showed me that this context-switching has too much overhead. But, I think these aren&rsquo;t the context switches we usually complain about, the ones that kill our flow when we jump between meetings and deep work. These switches are within the same context we&rsquo;re already working in. We&rsquo;re just tilting our perspective, changing how we evaluate the situation. And in my experience, the cost of wearing the wrong hat is far higher than the cost of being intentional about switching.</p>
]]></content:encoded></item><item><title>Working with Go's test cache on CI — be fast by avoiding work, while doing the important work</title><link>https://bjorn.now/blog/2025/07/07/working-with-gos-test-cache-on-ci/?utm_medium=feed</link><pubDate>Mon, 07 Jul 2025 21:50:00 +0800</pubDate><guid>https://bjorn.now/blog/2025/07/07/working-with-gos-test-cache-on-ci/</guid><description>I was trying to speed up our slow CI by caching Go builds. The easy win was caching Go’s module downloads (via GOPATH), but when I added GOCACHE for the build cache, I got a pleasant surprise: the tests were caching too. 🥳
I shared the change for review, and a colleague raised a great point: “What about our black box integration tests?” These tests hit APIs and external services that Go can’t track as dependencies. If they cache when they shouldn’t, we might miss real failures: the tests would pass because they didn’t re-run, not because the code actually works.</description><content:encoded><![CDATA[<p>I was trying to speed up our slow CI by caching Go builds. The easy win was caching Go&rsquo;s module downloads (via <code>GOPATH</code>), but when I added <code>GOCACHE</code> for the build cache, I got a pleasant surprise: the tests were caching too. 🥳</p>
<p>I shared the change for review, and a colleague raised a great point: &ldquo;What about our black box integration tests?&rdquo; These tests hit APIs and external services that Go can&rsquo;t track as dependencies. If they cache when they shouldn&rsquo;t, we might miss real failures: the tests would pass because they didn&rsquo;t re-run, not because the code actually works.</p>
<details>
<summary><strong>TL;DR:</strong> I found three ways to handle Go test caching on CI, they are:</summary>
<table>
  <thead>
      <tr>
          <th>Approach</th>
          <th>Changes Needed</th>
          <th>Test Caching</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>go clean -testcache</code></td>
          <td>CI config only</td>
          <td>❌ None</td>
      </tr>
      <tr>
          <td><code>-count=1</code></td>
          <td>Every test invocation</td>
          <td>❌ None</td>
      </tr>
      <tr>
          <td>Environment variables</td>
          <td>Shared code/library once</td>
          <td>✅ Full</td>
      </tr>
  </tbody>
</table>
<p><strong><code>go clean -testcache</code></strong> — wipe all test cache before running. No code changes, no test caching.</p>
<p><strong><code>-count=1</code></strong> — skip cache for a specific <code>go test</code> invocation. Need to add it everywhere.</p>
<p><strong>Environment variables</strong> — read your CI&rsquo;s commit SHA (e.g. <code>GITHUB_SHA</code>, <code>DRONE_COMMIT_SHA</code>) in your test setup. Go auto-invalidates the package&rsquo;s test cache when the value changes. Unit tests still cache, integration tests re-run on new commits as long as you read the variable once in the package. A helper to ensure it doesn&rsquo;t cache:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">IsIntegrationTest</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">t</span><span class="p">.</span><span class="nf">Helper</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">_</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nf">Getenv</span><span class="p">(</span><span class="s">&#34;GITHUB_SHA&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div>
</div>
<p>Key behavior:</p>
<ul>
<li>Invalidation is <strong>per-package</strong>, not per-test</li>
<li>Reading env vars through library calls still triggers invalidation</li>
<li><code>go test</code> with no package target never caches</li>
<li>Debug caching decisions with <code>GODEBUG=gocachetest=1</code></li>
</ul>
</details>
<h2 id="the-fundamental-problem">
  The Fundamental Problem
  <a href="#the-fundamental-problem" class="heading-anchor" aria-label="Direct link to The Fundamental Problem"><span aria-hidden="true">#</span></a>
</h2>
<p>When my colleague warned me about integration test caching, I realized we had a bigger problem than just my PR. How could I fix this across our entire org? We&rsquo;ve got ~250 repos, and asking every team to remember special flags or change their test execution wasn&rsquo;t going to fly. I needed something that would just work everywhere with minimal changes.</p>
<p>The fundamental issue: our black box integration tests don&rsquo;t &ldquo;declare their dependencies&rdquo; to the Go compiler, so it doesn&rsquo;t know that something the test is critically dependent upon has changed and requires a rerun (and practically, neither do we really), because they test network services and external binaries that Go has no visibility into. I always have to run these tests.</p>
<p>I wanted the caching benefits without the risk, ideally with minimal changes since we have lots of codebases that could benefit from this caching improvement. Since our black box integration tests use a shared internal library, I was hoping to find a solution I could implement once there rather than asking teams to modify individual tests or change how they execute them.</p>
<h2 id="so-how-do-i-make-this-work">
  So&hellip; How Do I Make This Work?
  <a href="#so-how-do-i-make-this-work" class="heading-anchor" aria-label="Direct link to So&hellip; How Do I Make This Work?"><span aria-hidden="true">#</span></a>
</h2>
<p>I started by looking for the obvious solutions, there&rsquo;s probably a flag to ignore the test cache, right? And there were two straightforward options: <code>go clean -testcache</code> and <code>-count=1</code>. But here&rsquo;s the thing, both of them throw away ALL test caching, which felt like using a sledgehammer when I needed a scalpel.</p>
<p>Then I remembered reading something about environment variables, and files, affecting test caching. I went into <code>go help test</code> and I remembered right, tests that read environment variables get invalidated when those variables change, so since this is about CI, and I know that CI systems give us unique commit SHAs as env vars… and we have a shared library to help write black box integration tests… so I can make the fix once in that library and everyone gets this benefit!</p>
<p>Alright, that gives me three options, how do they compare?</p>
<p><strong>Simple Option: <code>go clean -testcache</code></strong></p>
<ul>
<li>Pro: Dead simple - add one line to your CI config, no test changes needed</li>
<li>Con: Zero caching benefit for tests</li>
<li>When to use: Safest solution that still gives you build benefits without changing scripts or tests</li>
</ul>
<p>This works by creating <a href="https://github.com/golang/go/blob/665af869920432879629c1d64cf59f129942dcd6/src/cmd/go/internal/test/test.go#L844-L848">a file with the current Unix time in nanoseconds</a> and then, whenever Go runs tests, it checks if the cached test is newer than that timestamp. I appreciate the simplicity: the cache cleaner doesn&rsquo;t need to understand which cached items are tests (and there are only hashes in that folder), it just sets a &ldquo;tests are invalid after this moment&rdquo; marker and the code skips it or not.</p>
<p><strong>Explicit Option: <code>-count=1</code></strong></p>
<ul>
<li>Pro: Granular control per <code>go test</code> invocation</li>
<li>Con: Need to change test execution and remember it everywhere, easy to miss (especially in monorepos with many go.mods)</li>
<li>When to use: Only some tests need cache invalidation</li>
</ul>
<p>This is the one you&rsquo;ll see when people ask how to make Go not cache tests, for our situation not super useful, but mentioning it for completeness.</p>
<p><strong>Elegant Option: Environment Variables</strong></p>
<ul>
<li>Pro: Automatic cache invalidation on CI when needed, maximum caching benefit when possible</li>
<li>Con: Requires thoughtful test design</li>
<li>When to use: You have enough tests that the speedup matters and you&rsquo;re willing to be intentional about cache invalidation</li>
</ul>
<p>Or laid out visually:</p>
<table>
  <thead>
      <tr>
          <th>Approach</th>
          <th>Changes Needed</th>
          <th>Test Caching</th>
          <th>Safety</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>go clean -testcache</code></td>
          <td>CI config only</td>
          <td>❌ None</td>
          <td>✅ Safest</td>
      </tr>
      <tr>
          <td><code>-count=1</code></td>
          <td>Every test invocation</td>
          <td>❌ None</td>
          <td>✅ Safe</td>
      </tr>
      <tr>
          <td>Environment variables</td>
          <td>Shared code/library once</td>
          <td>✅ Full</td>
          <td>✅ Safe*</td>
      </tr>
  </tbody>
</table>
<p>*<em>When implemented correctly</em></p>
<h3 id="how-the-environment-variable-approach-works">
  How the Environment Variable Approach Works
  <a href="#how-the-environment-variable-approach-works" class="heading-anchor" aria-label="Direct link to How the Environment Variable Approach Works"><span aria-hidden="true">#</span></a>
</h3>
<p>This works because Go invalidates the entire package&rsquo;s test cache whenever an environment variable it depends on changes, and most CI systems expose a unique commit SHA as an environment variable. For example,</p>
<ul>
<li>GitHub Actions: <code>GITHUB_SHA</code></li>
<li>Drone CI: <code>DRONE_COMMIT_SHA</code></li>
</ul>
<p>When your integration test reads this variable, Go automatically invalidates the cache for that package whenever you push new code. The key insight: you don&rsquo;t need to <em>use</em> the variable meaningfully, just <em>read</em> it. One thing to keep in mind: consider keeping integration tests in separate packages from unit tests to avoid invalidating unit test caches unnecessarily when only the integration tests need cache invalidation.</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">integrationtesting</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">IsIntegrationTest</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">t</span><span class="p">.</span><span class="nf">Helper</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// This ensures that we invalidate the cache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// on CI whenever we have a new git SHA</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">_</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nf">Getenv</span><span class="p">(</span><span class="s">&#34;DRONE_COMMIT_SHA&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// ... another file relying on integrationtesting</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">TestAPIIntegration</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">integrationtesting</span><span class="p">.</span><span class="nf">IsIntegrationTest</span><span class="p">(</span><span class="nx">t</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Your actual integration test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">resp</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">callAPIEndpoint</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">assert</span><span class="p">.</span><span class="nf">Equal</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">expected</span><span class="p">,</span><span class="w"> </span><span class="nx">resp</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div>
</div>
<h2 id="understanding-how-it-actually-works">
  Understanding How It Actually Works
  <a href="#understanding-how-it-actually-works" class="heading-anchor" aria-label="Direct link to Understanding How It Actually Works"><span aria-hidden="true">#</span></a>
</h2>
<p>I was getting worried about edge cases. The Go docs said tests won&rsquo;t cache if environment variables change, but what exactly did that mean? Would reading an env var in one test invalidate <em>all</em> tests everywhere? Just that package? What if the variable is read through a library call?</p>
<p>I did some searching and found the <a href="https://github.com/golang/go/blob/6c3b5a2798c83d583cb37dba9f39c47300d19f1f/src/internal/testlog/log.go#L49-L54">testlog package</a>, which is how Go implements the tracking of when env vars are read and files opened. That gave me a good sense of how it should behave, so I made an experiment to validate what I expected from the documentation and the code I found.</p>
<h3 id="the-experiment-results">
  The Experiment Results
  <a href="#the-experiment-results" class="heading-anchor" aria-label="Direct link to The Experiment Results"><span aria-hidden="true">#</span></a>
</h3>
<p>I created <a href="https://github.com/gaqzi/experiment-go-test-caching">three test packages</a> to understand the boundaries:</p>
<ul>
<li>One that reads <code>DRONE_COMMIT_SHA</code> directly</li>
<li>One that reads it through a library call</li>
<li>One that doesn&rsquo;t read any environment variables</li>
</ul>
<p>Then I ran <a href="https://github.com/gaqzi/experiment-go-test-caching/blob/main/test.sh">nine scenarios to test my assumptions</a>: What happens when I set the variable? Does package listing matter (<code>./...</code> vs manual package lists)? What about <code>go test</code> with no arguments?</p>
<p>Key findings:</p>
<ul>
<li><strong>Package-level invalidation</strong>: All tests in a package get invalidated if <em>any</em> test reads the variable and the variable changes,</li>
<li><strong>Library calls count</strong>: Reading env vars through dependencies still invalidates the cache</li>
<li><strong>Package listing doesn&rsquo;t matter</strong>: <code>./...</code>, manual lists, even <code>./</code> all cache the same way</li>
<li><strong>No arguments = no cache</strong>: <code>go test</code> without targeting never caches</li>
<li><strong>File-level targeting = no cache</strong>: <code>go test file_test.go</code> also never caches</li>
</ul>
<p>The big takeaway: Go runs tests per package, and packages are run in parallel. If any test in the package touches an environment variable, the entire package&rsquo;s cache depends on that variable&rsquo;s value, so the cache is invalidated when the value is different on the next run.</p>
<p>Or as a semi-flowchart:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Package reads env var? 
</span></span><span class="line"><span class="cl">    Yes → Env var value changed? 
</span></span><span class="line"><span class="cl">              Yes → Cache invalid
</span></span><span class="line"><span class="cl">              No  → Cache valid
</span></span><span class="line"><span class="cl">    No → Cache valid</span></span></code></pre></div>
</div>
<p>This made me comfortable that the approach was predictable and wouldn&rsquo;t have surprising edge cases, especially because the way <a href="https://github.com/golang/go/blob/6c3b5a2798c83d583cb37dba9f39c47300d19f1f/src/internal/testlog/log.go#L49-L54">testlog</a> works is by simply recording that <em>something</em> called <code>os.Getenv</code> with this value, and it doesn&rsquo;t know which test or from where, just that in the course of running these tests this happened. Nice and simple.</p>
<p>If you want to explore how Go makes decisions around caching yourself, you can run tests with <code>GODEBUG=gocachetest=1</code> to see Go&rsquo;s caching decisions in real time. There are more debugging variables available in <code>go help cache</code>.</p>
<h2 id="the-results">
  The Results
  <a href="#the-results" class="heading-anchor" aria-label="Direct link to The Results"><span aria-hidden="true">#</span></a>
</h2>
<p>We cut our CI times in half with this caching approach, and I&rsquo;m confident it&rsquo;s safe to roll out widely because I understand exactly how it works and I can make the required changes to our shared integration testing library so it will just work for the standard case.</p>
<p>What I appreciate about Go&rsquo;s design here is how it avoids clever optimizations in favor of predictable behavior. I knew caching wasn&rsquo;t just on/off, but I wasn&rsquo;t sure if there would be complex edge cases to worry about. Instead, Go picks the straightforward solution: package-level invalidation that&rsquo;s easy to reason about.</p>
<p>The &ldquo;bigger packages than you think, but still not huge&rdquo; philosophy means we get plenty of caching benefit, while keeping the invalidation scope manageable, and if we need finer control, we can always split into more packages.</p>
<p>And that&rsquo;s the thing, Go just picks the simple and boring solution that works, it&rsquo;s why I love working with Go. 😁</p>
<hr>
<p><em>The <a href="https://github.com/gaqzi/experiment-go-test-caching">full experiment is available</a> if you want to dig into it yourself.</em><br>
<em><a href="https://speakerdeck.com/gaqzi/working-with-gos-test-cache-on-ci">Presentation slides</a> from the Singapore Go meetup</em></p>
]]></content:encoded></item><item><title>Running effective meetings uses the same skills as running successful projects — From meeting minutes to project milestones</title><link>https://bjorn.now/blog/2025/05/03/effective-meetings/?utm_medium=feed</link><pubDate>Sat, 03 May 2025 16:20:00 +0800</pubDate><guid>https://bjorn.now/blog/2025/05/03/effective-meetings/</guid><description>You’ve probably sat through your fair share of meetings where time slips away as conversations wander aimlessly, not unlike being dropped into an unfamiliar codebase, with twisting branches all alike.
A colleague asked for feedback on a meeting they ran, and after sharing my thoughts, I realized my advice about meetings sounded exactly like how I talk about planning projects. This got me thinking: the skills for running good meetings and successful projects are nearly identical.</description><content:encoded><![CDATA[<p>You&rsquo;ve probably sat through your fair share of meetings where time slips away as conversations wander aimlessly, not unlike being dropped into an unfamiliar codebase, with twisting branches all alike.</p>
<p>A colleague asked for feedback on a meeting they ran, and after sharing my thoughts, I realized my advice about meetings sounded exactly like how I talk about planning projects. This got me thinking: the skills for running good meetings and successful projects are nearly identical.</p>
<p>Yet somehow, we still treat our meetings like they&rsquo;re different beasts entirely. Why do so many meetings fail? Usually because we approach them without a clear plan.</p>
<p>Meetings are often run like debugging without a hypothesis, just throwing log statements everywhere and hoping something works, where you have a list of topics but no clear goals or time constraints. You might cover everything, but that&rsquo;s more luck than design.</p>
<p>This is similar to &ldquo;planning forward&rdquo; in projects: We start with a feature list without understanding the details, create up-front timelines in Gantt charts that rarely get revised, and postpone integration until the end. We move through tasks sequentially, assuming everything will go right, only to discover problems when time is about to or has already run out.</p>
<p>Both approaches ignore that time runs out, whether in a 30-minute meeting or a three-month project. Without a plan, meetings go nowhere and projects fall behind.</p>
<p>In my experience, here&rsquo;s what both have in common:</p>
<ul>
<li>Gather everything that needs to get done</li>
<li>Clarify goals for each item</li>
<li>Group similar things together</li>
<li>Prioritize the important stuff first</li>
<li>Estimate how long each section will take</li>
<li>Guide the work to keep it on track</li>
<li>Define next steps so everyone knows what&rsquo;s happening after</li>
</ul>
<h2 id="the-pieces">
  The pieces
  <a href="#the-pieces" class="heading-anchor" aria-label="Direct link to The pieces"><span aria-hidden="true">#</span></a>
</h2>
<p>Let&rsquo;s break down each of these components and see how they apply specifically to meetings:</p>
<h3 id="1-gather-define-your-scope">
  1. Gather: Define your scope
  <a href="#1-gather-define-your-scope" class="heading-anchor" aria-label="Direct link to 1. Gather: Define your scope"><span aria-hidden="true">#</span></a>
</h3>
<p>Just as you wouldn&rsquo;t start coding without requirements, don&rsquo;t start a meeting without defining what needs covering. List all topics and be realistic about what fits in the allotted time. If your meeting scope looks like your backlog after a year of &ldquo;we&rsquo;ll get to it later,&rdquo; you&rsquo;re doing it wrong.</p>
<h3 id="2-clarify-set-measurable-goals-for-each-topic">
  2. Clarify: Set measurable goals for each topic
  <a href="#2-clarify-set-measurable-goals-for-each-topic" class="heading-anchor" aria-label="Direct link to 2. Clarify: Set measurable goals for each topic"><span aria-hidden="true">#</span></a>
</h3>
<p>For each topic, be clear about what you&rsquo;re trying to accomplish:</p>
<ul>
<li><strong>Discuss:</strong> &ldquo;We need to decide on auth providers. What criteria should guide our recommendation?&rdquo;</li>
<li><strong>Decision:</strong> &ldquo;We recommend Provider A because&hellip;&rdquo;</li>
<li><strong>FYI:</strong> &ldquo;We&rsquo;ve implemented the new provider in staging and learned&hellip;&rdquo;</li>
</ul>
<p><strong>Pro tip</strong>: Prefix each agenda item with one of these categories (Discuss/Decision/FYI) to ground everyone in what you&rsquo;re trying to accomplish with each topic.</p>
<h3 id="3-group-combine-related-topics">
  3. Group: Combine related topics
  <a href="#3-group-combine-related-topics" class="heading-anchor" aria-label="Direct link to 3. Group: Combine related topics"><span aria-hidden="true">#</span></a>
</h3>
<p>Identify when similar items come from different people and combine them. This prevents redundant discussions and gives a clearer picture of what needs attention, similar to combining related user stories into an epic. When several people raise the same issue in different ways, it&rsquo;s usually important.</p>
<h3 id="4-prioritize-focus-on-what-matters">
  4. Prioritize: Focus on what matters
  <a href="#4-prioritize-focus-on-what-matters" class="heading-anchor" aria-label="Direct link to 4. Prioritize: Focus on what matters"><span aria-hidden="true">#</span></a>
</h3>
<p>Sequence by importance, not by who talks loudest or which item has been sitting on the agenda longest. Start with critical items so when you inevitably run out of time, at least the important stuff got done.</p>
<h3 id="5-estimate-time-boxing">
  5. Estimate: Time boxing
  <a href="#5-estimate-time-boxing" class="heading-anchor" aria-label="Direct link to 5. Estimate: Time boxing"><span aria-hidden="true">#</span></a>
</h3>
<p>We accept that iteration estimates will be wrong (it&rsquo;s just a question of how wrong), and meeting time estimates work exactly the same way. Having a clear time target keeps everyone aligned. Be explicit about constraints: &ldquo;We have 15 minutes for this topic&rdquo; creates healthy pressure to stay focused.</p>
<h3 id="6-guide-keep-discussions-on-track">
  6. Guide: Keep discussions on track
  <a href="#6-guide-keep-discussions-on-track" class="heading-anchor" aria-label="Direct link to 6. Guide: Keep discussions on track"><span aria-hidden="true">#</span></a>
</h3>
<p>Just like daily standups in project work, regularly check in on your meeting&rsquo;s progress. Help nudge the meeting along:</p>
<h4 id="monitor-progress-toward-goals">
  Monitor progress toward goals
  <a href="#monitor-progress-toward-goals" class="heading-anchor" aria-label="Direct link to Monitor progress toward goals"><span aria-hidden="true">#</span></a>
</h4>
<p>Just like in daily standups, pay attention to whether the discussion is moving toward your goal. Ask yourself, &ldquo;Are we just talking, or are we getting closer to making a decision?&rdquo; If you&rsquo;re 10 minutes into database options and no one has mentioned the actual requirements, it&rsquo;s time to refocus.</p>
<h4 id="keep-discussions-focused">
  Keep discussions focused
  <a href="#keep-discussions-focused" class="heading-anchor" aria-label="Direct link to Keep discussions focused"><span aria-hidden="true">#</span></a>
</h4>
<p>When someone goes off on a tangent describing an intricate edge case, redirect with: &ldquo;That sounds like an important edge case. Will this affect the decision we&rsquo;re trying to make right now? If not, could you chat with the relevant team after this? Right now we need to focus on making our decision.&rdquo;</p>
<h4 id="provide-time-checks">
  Provide time checks
  <a href="#provide-time-checks" class="heading-anchor" aria-label="Direct link to Provide time checks"><span aria-hidden="true">#</span></a>
</h4>
<p>My <a href="https://www.geepawhill.org/2021/04/07/rice-garlic-more-smaller-steps/">rice&rsquo;n&rsquo;garlic advice</a> is to check in halfway through a conversation and remind everyone of the time left. Sometimes simply saying &ldquo;We have 7 minutes left to decide on this&rdquo; creates the right pressure to wrap things up. If needed, quickly restate the goal: &ldquo;Remember, we just need to agree on which two options to research further, not make the final decision today.&rdquo;</p>
<h4 id="make-realistic-time-decisions">
  Make realistic time decisions
  <a href="#make-realistic-time-decisions" class="heading-anchor" aria-label="Direct link to Make realistic time decisions"><span aria-hidden="true">#</span></a>
</h4>
<p>Be realistic about whether you can finish in the time you have. If a 15-minute agenda item is turning into a 45-minute discussion, decide whether to:</p>
<ul>
<li>Extend the current meeting (if everyone can stay)</li>
<li>Schedule a follow-up with just the key stakeholders</li>
<li>Assign someone to gather more data and come back next time</li>
</ul>
<h3 id="7-next-steps-ensure-clear-outcomes">
  7. Next steps: Ensure clear outcomes
  <a href="#7-next-steps-ensure-clear-outcomes" class="heading-anchor" aria-label="Direct link to 7. Next steps: Ensure clear outcomes"><span aria-hidden="true">#</span></a>
</h3>
<p>Don&rsquo;t let topics end without everyone knowing the outcome. For each item, ensure everyone knows:</p>
<ul>
<li>Is it done or still open?</li>
<li>Who owns any follow-up actions?</li>
<li>When should we expect progress?</li>
<li>How will we track it?</li>
</ul>
<p>Capture this in your meeting notes. Don&rsquo;t rely on memory. Write down the decisions, owners, and deadlines so our future selves can thank us.</p>
<h2 id="in-practice">
  In practice
  <a href="#in-practice" class="heading-anchor" aria-label="Direct link to In practice"><span aria-hidden="true">#</span></a>
</h2>
<p>Different meetings need different approaches, but a well-run meeting follows the same meta structure. This structure works for all kinds of meetings: one-on-ones, planning sessions, even technical architecture reviews, because the topics and participants change, but the structure that makes meetings effective doesn&rsquo;t.</p>
<p>Let&rsquo;s see how this structure looks in practice with a couple of common meeting types.</p>
<h3 id="retrospectives">
  Retrospectives
  <a href="#retrospectives" class="heading-anchor" aria-label="Direct link to Retrospectives"><span aria-hidden="true">#</span></a>
</h3>
<p>Retros help teams reflect on what worked, what didn&rsquo;t, and what to improve, much like code reviews but for your processes.</p>
<ul>
<li><strong>Gather</strong>: Have everyone add topics to a shared retrospective board</li>
<li><strong>Clarify &amp; Group</strong>: Cluster related topics to understand common themes</li>
<li><strong>Prioritize</strong>: Vote on which issues will have the most impact to address</li>
<li><strong>Estimate &amp; Guide</strong>: Timebox discussions, default 3 minutes per topic, explicitly decide to extend or move on</li>
<li><strong>Next Steps</strong>: Capture clear action items, owners, and timelines</li>
</ul>
<p><strong>Pro tip</strong>: Seeing the same issues iteration after iteration? Try a <a href="https://www.thoughtworks.com/en-au/insights/blog/reverse-retrospective-part-1">reverse retrospective</a> where you start with known problems and dedicate the entire session to brainstorming solutions.</p>
<h3 id="recurring-team-meetings">
  Recurring team meetings
  <a href="#recurring-team-meetings" class="heading-anchor" aria-label="Direct link to Recurring team meetings"><span aria-hidden="true">#</span></a>
</h3>
<p>Regular meetings work best with a consistent structure, like good software needs a reliable architecture.</p>
<ul>
<li><strong>Gather</strong>: Maintain a living document where people add items between meetings, plus time at the start to add more</li>
<li><strong>Prioritize</strong>: Critical conversations happen first, not just what&rsquo;s been on the list longest</li>
<li><strong>Clarify</strong>: Define each topic&rsquo;s goal and intended outcome (FYI, discussion, or decision-making)</li>
<li><strong>Estimate</strong>: Allocate time to topics based on importance and complexity</li>
<li><strong>Guide</strong>: Check progress mid-topic with &ldquo;Are we getting closer to our goal?&rdquo;</li>
<li><strong>Next Steps</strong>: Note who&rsquo;s doing what by when</li>
</ul>
<p><strong>Pro tip</strong>: For complex topics, try a <a href="https://medium.com/swlh/the-silent-meeting-manifesto-v1-189e9e3487eb">silent meeting</a> approach where everyone reads and comments on a shared document before discussion. This helps people think deeply, solve quick questions in writing, and prevent a few voices from dominating.</p>
<h2 id="the-meta-skill">
  The meta-skill
  <a href="#the-meta-skill" class="heading-anchor" aria-label="Direct link to The meta-skill"><span aria-hidden="true">#</span></a>
</h2>
<p>Running a good meeting uses the same mental muscles as running a good project:</p>
<ul>
<li>Scope definition is like requirements gathering</li>
<li>Timeboxing topics is like defining iterations</li>
<li>Progress checks are like standups</li>
</ul>
<p>If you can&rsquo;t keep a 30-minute meeting on track, how will you keep a three-month project on track? Both require the same core skills:</p>
<ol>
<li>
<p><strong>Clear Goals</strong>: &ldquo;We want to discuss database options&rdquo; is as useless as &ldquo;We want to build a better app.&rdquo;</p>
</li>
<li>
<p><strong>Risk Management</strong>: Put risky, important topics first, whether in meetings or project timelines.</p>
</li>
<li>
<p><strong>Progress Monitoring</strong>: Regularly ask: &ldquo;Are we moving toward our goal or getting sidetracked?&rdquo;</p>
</li>
<li>
<p><strong>Flexibility with Discipline</strong>: Sometimes you need to extend a meeting or an iteration, but do it consciously, not by default.</p>
</li>
<li>
<p><strong>Follow-through</strong>: Track action items like you track bugs, they&rsquo;re just as important.</p>
</li>
</ol>
<p>I wonder if running meetings could be good practice for running projects? The skills are similar, but the feedback cycle is much shorter.</p>
<h2 id="conclusion">
  Conclusion
  <a href="#conclusion" class="heading-anchor" aria-label="Direct link to Conclusion"><span aria-hidden="true">#</span></a>
</h2>
<p>Every engineer knows that a well-designed system needs both good architecture and regular maintenance. Your meetings are no different.</p>
<p>By applying the same structured thinking to your meetings that you use in your code (defining clear requirements, managing scope, prioritizing effectively, and documenting outcomes), you transform the meetings in your calendar into productive work sessions.</p>
<p>In my experience, these skills compound nicely. I bet you&rsquo;ll get better at delivering focused projects as you get better at running focused meetings.</p>
<p>So before you schedule your next meeting, run it through the same mental checklist you&rsquo;d use for planning a project:</p>
<ul>
<li>Have you gathered topics and defined what you want from each one?</li>
<li>Have you prioritized what matters most?</li>
<li>Can this all get done in the time you have?</li>
<li>Do you have a plan to keep discussions on track?</li>
</ul>
<p>Keep in mind that guiding meetings is a lot like incident response. You can plan all you want, but you need to react to what&rsquo;s actually happening. This framework gives you a starting point, but the real magic happens when you apply it in the moment. Start small by focusing on timeboxing discussions or defining clear outcomes. The structure matters, but how you adapt when the sewage hits the aerowatt generator is what really counts.</p>
<p>If not, refactor before you commit.</p>
<p>Efficient meetings aren&rsquo;t magic. They&rsquo;re just good engineering applied to communication.</p>
]]></content:encoded></item><item><title>Day-to-day automation using Alfred on your Mac — reducing TOIL outside of your DevOps practice</title><link>https://bjorn.now/blog/2024/04/15/day-to-day-automation-using-alfred/?utm_medium=feed</link><pubDate>Mon, 15 Apr 2024 19:15:00 +0800</pubDate><guid>https://bjorn.now/blog/2024/04/15/day-to-day-automation-using-alfred/</guid><description>I try to be aware of where I have friction in my day-to-day when working at my computer, keeping an eye out for TOIL to remove. Whether it’s from manually repeated actions, or from differing behaviors across apps and services I use. When I identify any of these I try to spend some small amount of time automating or changing how I work to improve.
All of these things don’t necessarily make a lot of sense from how much time I save, but, it also makes me happy to spend a bit of time improving my environment, and spending a bit of time to make me happy is good enough for me.</description><content:encoded><![CDATA[<p>I try to be aware of where I have friction in my day-to-day when working at my
computer, keeping an eye out for <a href="https://sre.google/sre-book/eliminating-toil/">TOIL to remove.</a> Whether it&rsquo;s from
manually repeated actions, or from differing behaviors across apps and services
I use. When I identify any of these I try to spend some small amount of time
automating or changing how I work to improve.</p>
<p>All of these things don&rsquo;t necessarily make a lot of sense from how much time I
save, but, it also makes me happy to spend a bit of time improving my
environment, and spending a bit of time to make me happy is good enough for me.</p>
<p>I view doing these kinds of small workflow improvements like a <a href="https://en.wikipedia.org/wiki/Kata#Outside_martial_arts">kata</a> because it
improves my automation/scripting skills and encourages me to think about how I
work. I usually give myself 10-20m to try and make these kinds of improvements
just so I know how much time to spend. Just don&rsquo;t go overboard. XKCD, as always,
have a comic to illustrate the downside of going too far. 🙂</p>
<p><a href="https://xkcd.com/1319"><img src="https://imgs.xkcd.com/comics/automation.png" alt="&lsquo;Automating&rsquo; comes from the roots &lsquo;auto-&rsquo; meaning &lsquo;self-&rsquo;, and &lsquo;mating&rsquo;, meaning &lsquo;screwing&rsquo;.&quot;"></a></p>
<h2 id="what-is-alfred-and-why-is-it-an-app-im-focusing-on">
  What is Alfred and why is it an app I&rsquo;m focusing on?
  <a href="#what-is-alfred-and-why-is-it-an-app-im-focusing-on" class="heading-anchor" aria-label="Direct link to What is Alfred and why is it an app I&rsquo;m focusing on?"><span aria-hidden="true">#</span></a>
</h2>
<p><a href="https://alfredapp.com/">Alfred</a> is an application launcher but oh-so-much-more. It connects with macOS
and the Workflows contains a lot of extensions that allow you to interact with
all kinds of programs, websites, etc.</p>
<p>A big part of what Alfred has helped me create is a more unified experience
across the apps and websites I use. I have moved some operations that behave
differently in different apps into Alfred because then I can focus on what I
need to do and then use Alfred to provide consistency (and as I&rsquo;ve
been told, &ldquo;consistency is good even outside the bathroom&rdquo;).</p>
<p>Now, let&rsquo;s look at how I use Alfred.</p>
<h2 id="built-in-functionality">
  Built-in functionality
  <a href="#built-in-functionality" class="heading-anchor" aria-label="Direct link to Built-in functionality"><span aria-hidden="true">#</span></a>
</h2>
<p>These are features that don&rsquo;t require anything extra to install and use.</p>
<h3 id="open-an-app">
  Open an app
  <a href="#open-an-app" class="heading-anchor" aria-label="Direct link to Open an app"><span aria-hidden="true">#</span></a>
</h3>
<p>Alfred&rsquo;s starter feature is the application launcher, and it means you summon it
with a keybinding and type the name of the app you want to start and voilà.
I have bound the keyboard shortcut to <code>cmd+space</code> and replaced the built-in
Spotlight search.</p>
<figure><img src="/img/2024/04/15/01-open-app.png"
    alt="Opening Firefox in Alfred">
</figure>

<h3 id="spell--define">
  Spell / define
  <a href="#spell--define" class="heading-anchor" aria-label="Direct link to Spell / define"><span aria-hidden="true">#</span></a>
</h3>
<p>There are certain words I rarely get right (continuous) and some
words are spelled very differently from how they sound. Summon Alfred and
<code>spell &lt;word&gt;</code> and you&rsquo;ll have suggestions that often will go right. And if
you don&rsquo;t find it, replace <code>spell</code> with <code>google</code> and see if Google knows
better. Hit enter to copy the correct spelling to the clipboard.</p>
<figure><img src="/img/2024/04/15/02-spell-continuous.png"
    alt="Finding the correct spelling of continuus">
</figure>

<p>And <code>define</code> is the same thing but for dictionary lookup because you have some
word you&rsquo;re unsure what it means, or because you&rsquo;re wondering if someone was
trying to call you something you with some subtle nuance to a word. 😉
Hit enter to open the definition in macOS&rsquo;s dictionary app.</p>
<figure><img src="/img/2024/04/15/03-define-laggard.png"
    alt="Define &#39;laggard&#39;">
</figure>

<h3 id="calculator">
  Calculator
  <a href="#calculator" class="heading-anchor" aria-label="Direct link to Calculator"><span aria-hidden="true">#</span></a>
</h3>
<p>You can give it an expression and when you hit enter the result goes into the
clipboard. I often end up using this instead of opening a Python console on the
CLI.</p>
<figure><img src="/img/2024/04/15/04-calculator.png"
    alt="Adding some numbers together">
</figure>

<h3 id="custom-web-search">
  Custom web search
  <a href="#custom-web-search" class="heading-anchor" aria-label="Direct link to Custom web search"><span aria-hidden="true">#</span></a>
</h3>
<p>A custom web search allow you to quickly open a web browser at a
particular website with whatever value you&rsquo;ve typed into Alfred which <em>doesn&rsquo;t
have to be a search engine or even a search page.</em></p>
<p>To configure a custom web search summon Alfred and hit <code>cmd+,</code> to open
the settings page. Go into the <code>Features</code> tab and click on <code>Web Search</code>.</p>
<figure><img src="/img/2024/04/15/05-web-search-listing.png"
    alt="Alfred&#39;s setting Features -&gt; Web Search highlighted">
</figure>

<p>Some example search engines I have made:</p>
<ul>
<li>Jira: <code>https://jira.example.com/browse/{query}</code>
<ul>
<li>This isn&rsquo;t a search but rather the view this ticket. Because I often get a
ticket id (either from a commit message or someone rattling it off in a
meeting) and this way I can open it quickly</li>
</ul>
</li>
<li>Confluence: <code>https://confluence.example.com/dosearchsite.action? queryString={query}</code></li>
<li>Wordnik: <code>https://wordnik.com/words/{query}</code>
<ul>
<li>So I can find synonyms and other alternatives for words. Often use this when
I want to find a good name for something which has been inspired
by <a href="https://signalvnoise.com/svn3/hunting-for-great-names-in-programming/">Hunting for great names in programming</a>.</li>
</ul>
</li>
<li>Pypi: <code>https://pypi.org/project/{query}</code>
<ul>
<li>To look up python packages</li>
</ul>
</li>
</ul>
<figure><img src="/img/2024/04/15/06-web-search-example-config.png"
    alt="Example setup of the Confluence search engine">
</figure>

<h2 id="working-with-other-applications">
  Working with other applications
  <a href="#working-with-other-applications" class="heading-anchor" aria-label="Direct link to Working with other applications"><span aria-hidden="true">#</span></a>
</h2>
<p>Alfred has ended being the tool that brings my apps to focus and smoothes
over differences between apps.</p>
<h3 id="give-my-most-common-apps-a-keybinding">
  Give my most common apps a keybinding
  <a href="#give-my-most-common-apps-a-keybinding" class="heading-anchor" aria-label="Direct link to Give my most common apps a keybinding"><span aria-hidden="true">#</span></a>
</h3>
<p>Instead of <code>cmd+tab</code> between the various apps I&rsquo;m using I have bound specific
keybindings to my most used app, so I can quickly swap between
them. <a href="https://dev.to/rstacruz/switching-apps-slow-down-my-productivity-and-how-i-fixed-it-2anb">Hat tip to Rico Sta. Cruz</a>
who blogged about this a couple of years ago and who I picked it up from.</p>
<p>The big benefit I have found here is just the ease of being mid-thinking and
wanting to switch back to another app, whether it&rsquo;s to jot down a quick note, to
finish that Regex you were visualizing on <a href="https://regex101.com/">Regex 101</a>, or
to get back to Slack to paste that thing your colleague asked for. It feels so
seamless compared to tabbing between.</p>
<p>This is using <a href="https://www.alfredapp.com/workflows/">Alfred&rsquo;s Workflows</a>, and
they&rsquo;re part of their advanced set of options. You can do keybindings that work
across all of macOS as well as actions based on what you&rsquo;re typing.</p>
<figure><img src="/img/2024/04/15/07-app-shortcuts.png"
    alt="The custom app shortcuts workflow">
</figure>

<h3 id="spotify-controller">
  Spotify controller
  <a href="#spotify-controller" class="heading-anchor" aria-label="Direct link to Spotify controller"><span aria-hidden="true">#</span></a>
</h3>
<p>I have been using these keybindings since before Alfred, in Quicksilver, and
there are other workflows for Spotify but since I had these I never bothered
installing anything else.</p>
<p>The cool extra in this workflow is that I added a keyword to open the
current playing song on <a href="https://genius.com">Genius.com</a> because I like
reading the lyrics.</p>
<p>This is an example of something where the automation is <em>far from necessary,</em>
but, I enjoyed adding it. It&rsquo;s something I do every other week and knowing I
can just go straight into the song nearly every time is great.</p>
<figure><img src="/img/2024/04/15/08-spotify-keybindings.png"
    alt="Spotify keybinding and keyword configuration">
</figure>

<p>My <a href="https://github.com/gaqzi/alfred-workflows/blob/master/Spotify%20keyboard%20shortcuts.alfredworkflow">Spotify workflow</a> is on Github if you want to install it, or <a href="https://gist.github.com/gaqzi/b3de88050bf1caaf8d2661233b51a1ae">here&rsquo;s a gist</a>
if you&rsquo;re just curious how I made &ldquo;open on genius.&rdquo;</p>
<h3 id="global-microphone-muteunmute-shortcuts">
  Global microphone mute/unmute shortcuts
  <a href="#global-microphone-muteunmute-shortcuts" class="heading-anchor" aria-label="Direct link to Global microphone mute/unmute shortcuts"><span aria-hidden="true">#</span></a>
</h3>
<p>I regularly use Google Meet, Slack, and Zoom for talking with people, and these
apps all use different shortcuts to mute. So instead of keeping the app
focused and remembering the various shortcuts I now just leave myself unmuted
everywhere and directly mute/unmute the OS through Alfred. My mic has a visual
indicator when it&rsquo;s on. +I have a notification posted, so I can see the current
status on the screen.</p>
<p>As I started using this workflow I realized that sometimes the microphone
bugs out but swapping it between the built-in and back will fix it. So after
a while I made a script to help swap as well.</p>
<p>And finally, I realized that sometimes my virtual soundcard which removes
background noise (<a href="https://krisp.ai">krisp.ai</a>) hangs and needs to be
restarted, so another keybinding to quickly restart the soundcard.</p>
<p>The only downside of this workflow is that others can&rsquo;t as clearly see when
I want to speak by unmuting myself in the current app, so in those rare cases
I&rsquo;ll use the mute in the app itself. Because optimizing my workflow
shouldn&rsquo;t come at the pain of everyone else, we live in a society. 🙂</p>
<figure><img src="/img/2024/04/15/09-mic-switch.png"
    alt="The mic switch workflow setup">
</figure>

<p>This <a href="https://github.com/gaqzi/alfred-workflows/blob/master/Toggle%20mute%20mic.alfredworkflow">mute/unmute workflow</a> is available on Github,
and <a href="https://gist.github.com/gaqzi/f299047706c10b2616ba15c94cabf1fe">here&rsquo;s a gist</a> if you want to see the toggle code. My
dotfiles got the <a href="https://github.com/gaqzi/conf/blob/master/bin/swap-mic-input">swap mic script</a> and the <a href="https://github.com/gaqzi/conf/blob/master/bin/%2Crestart-krisp">restart krisp script</a>.</p>
<h2 id="improvement-to-my-day-to-day-workflow">
  Improvement to my day-to-day workflow
  <a href="#improvement-to-my-day-to-day-workflow" class="heading-anchor" aria-label="Direct link to Improvement to my day-to-day workflow"><span aria-hidden="true">#</span></a>
</h2>
<h3 id="text-replacement--snippets">
  Text replacement / snippets
  <a href="#text-replacement--snippets" class="heading-anchor" aria-label="Direct link to Text replacement / snippets"><span aria-hidden="true">#</span></a>
</h3>
<p>Snippets are short pieces of text that expand into something else. It can be
a static string, or it can be dynamic and I use both kinds as I work. For me
they fall into these categories</p>
<ol>
<li>&ldquo;Named paste&rdquo;, things that are hard to remember or unwieldy and are now not</li>
<li>Dynamic replacement, something that follows a pattern and I want to swap
between. Generally these use the clipboard</li>
<li>Generated inputs, I need the current time in a specific format or some
other piece of data</li>
</ol>
<p>You configure snippets by summoning Alfred and hitting <code>cmd+,</code> which opens
Settings, then go into Features -&gt; Snippets. Snippets work by specifying a
keyword, in my default bundle I require that my keywords are prefixed with <code>\\</code>,
meaning that Alfred will expand my snippets if it sees <code>\\&lt;keyword&gt;</code> and with
the example keyword <code>exp</code> you&rsquo;d then type <code>\\exp</code> to add it. If you need to type
the exact string for some reason it&rsquo;s easiest to add a space somewhere before
you type the last character and then go back and remove it.</p>
<figure><img src="/img/2024/04/15/11-snippets-config.png"
    alt="How to view or create snippets">
</figure>

<p>My trigger for creating a snippet is finding that I keep typing or
pasting the same thing again and again.</p>
<p>Let&rsquo;s look at how I use them in a couple of different places.</p>
<h4 id="gmail">
  GMail
  <a href="#gmail" class="heading-anchor" aria-label="Direct link to GMail"><span aria-hidden="true">#</span></a>
</h4>
<p>Finding emails from &ldquo;unusual senders:&rdquo; I label the majority of emails I get
at work as they are notifications or otherwise automated emails. So if I get
something that isn&rsquo;t labeled it&rsquo;s probably a human or something I need to
look into, and it can sometimes get hidden in a sea of everything else.</p>
<p><code>&lt;prefix&gt;gmailn</code> in the search bar turns into <code>is:inbox is:unread has:nouserlabels</code> which only shows unread emails in the inbox which doesn&rsquo;t
have a label.</p>
<p>When I&rsquo;m processing my labels, which are effectively buckets of things to do,
I often go into a label and want to see only the unread items, so I can open
the first and then go through them one-by-one. So open the label, focus on
the search bar, and type in <code>&lt;prefix&gt;unr</code> to give me <code>is:unread</code> and then
get to that focus.</p>
<h4 id="jira-tickets">
  Jira tickets
  <a href="#jira-tickets" class="heading-anchor" aria-label="Direct link to Jira tickets"><span aria-hidden="true">#</span></a>
</h4>
<p>If I&rsquo;m writing a message on Slack and I need to reference a ticket I think
it&rsquo;s nice to give a link even if I only have the jira ticket ID, so I made a
snippet that adds the current clipboard item at the end of my company&rsquo;s Jira
instance in a Markdown link. Now it looks nice where markdown is and the
other person doesn&rsquo;t have to open it up themselves.</p>
<p>Copy <code>WRK-123</code> and then type <code>&lt;prefix&gt;jiramd</code> which replaces it with
<code>[WRK-123](https://jira.deliveryhero.com/browse/WRK-123)</code>.</p>
<p>The Alfred snippet configuration</p>
<div class=" copy-enabled">
  <pre tabindex="0"><code>[{clipboard:trim}](https://jira.example.com/browse/{clipboard:trim})</code></pre>
</div>
<figure><img src="/img/2024/04/15/12-snippet-jiramd.png"
    alt="Configuring the jiramd snippet in Alfred">
</figure>

<h4 id="roam-research">
  Roam Research
  <a href="#roam-research" class="heading-anchor" aria-label="Direct link to Roam Research"><span aria-hidden="true">#</span></a>
</h4>
<p>Roam is my notetaking app and where most of my thinking happen and I have a
whole slew of replacements for how I work with Roam Research and most of them
are to help me keep my workflow consistent (using the right tags)
and to make common actions easier and faster.</p>
<ul>
<li><code>&lt;prefix&gt;rts</code> turns into the current time in bold since I practice
interstitial journaling. Example <code>**15:03**</code>. Snippet configuration: <code>** {time:short}**</code></li>
<li><code>&lt;prefix&gt;b&lt;shortcode&gt;</code> for how I bucket tasks so when I spend time on a
particular bucket I can find the things I need to do. I.e. my bucket  &ldquo;Life in
Production&rdquo; (on-call, observability, continuous delivery, etc.) is prefixed
with <code>&lt;prefix&gt;bl</code> and that turns into <code>[🥬]([[Bucket/Life in Production]])</code></li>
<li><code>&lt;prefix&gt;&lt;INITIALS&gt;</code> to turn into the name of my most frequent
collaborators at work, so I can tag what I&rsquo;ve said to them or a task we
need to do.</li>
</ul>
<h3 id="emoji-search">
  Emoji search
  <a href="#emoji-search" class="heading-anchor" aria-label="Direct link to Emoji search"><span aria-hidden="true">#</span></a>
</h3>
<p>Spending a lot of time communicating across continents involves timezones
and people rarely hearing your voice, so I have gotten used to peppering my
inputs with emojis to help since it&rsquo;s so easy to come off sounding like an
ass otherwise.</p>
<p>And because some websites or applications don&rsquo;t have a good search for
emoji and this way I only have to learn one keyboard shortcut and normalize it
across places. This way I don&rsquo;t have to remember how Slack named this emoji vs.
Miro vs. Google Docs vs. Confluence vs. other random website. I just learn it in
Alfred and then paste it straight into the textbox I wanted.</p>
<figure><img src="/img/2024/04/15/10-search-emoji.png"
    alt="Searching for the rocket emoji">
</figure>

<p>This is an external Workflow made by <a href="https://github.com/jsumners">James Sumners</a>, and you can download it
from the <a href="https://github.com/jsumners/alfred-emoji">workflow&rsquo;s Github page.</a></p>
<h3 id="generate-uuids-on-the-fly">
  Generate uuids on the fly
  <a href="#generate-uuids-on-the-fly" class="heading-anchor" aria-label="Direct link to Generate uuids on the fly"><span aria-hidden="true">#</span></a>
</h3>
<p>As I was writing the tying it up section I realized that I had a recent pain
which where I kept jumping between apps to create new UUIDs. I was running
the following command on the CLI and then pasting it into some yaml files in
my editor.</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">uuidgen <span class="p">|</span> tr <span class="s1">&#39;[:upper:]&#39;</span> <span class="s1">&#39;[:lower:]&#39;</span> <span class="p">|</span> tr -d <span class="s1">&#39;\n&#39;</span> <span class="p">|</span> pbcopy</span></span></code></pre></div>
</div>
<p>So I instead created a new workflow that is triggered by a snippet that runs
this for me.</p>
<figure><img src="/img/2024/04/15/15-snippet-uuidgen.png"
    alt="Configuring the uuidgen snippet">
</figure>

<p><strong>Note:</strong> To make it behave just like a regular snippet you need to
configure the <em>Copy to clipboard</em> output to <code>Automatically paste to the frontmost app</code> and <code>Mark item as transient in clipboard</code>.</p>
<figure><img src="/img/2024/04/15/16-snippet-uuidgen-clipboard-config.png"
    alt="How configure the Copy to clipboard output">
</figure>

<p>The <a href="https://github.com/gaqzi/alfred-workflows/blob/master/uuidgen.alfredworkflow">uuidgen workflow</a> is available on Github.</p>
<h2 id="other-workflows">
  Other workflows
  <a href="#other-workflows" class="heading-anchor" aria-label="Direct link to Other workflows"><span aria-hidden="true">#</span></a>
</h2>
<p>Some other workflows I have installed since they save me time.</p>
<h3 id="calculate-anything">
  Calculate Anything
  <a href="#calculate-anything" class="heading-anchor" aria-label="Direct link to Calculate Anything"><span aria-hidden="true">#</span></a>
</h3>
<p>I installed this for two reasons: to quickly translate cooking recipes using
imperial units into metric and currency conversion. Super convenient and now I
can finally get how much 16 floz is in ml (473.176) without needing to go
online.</p>
<figure><img src="/img/2024/04/15/13-calculate-anything.png"
    alt="Calculate Anything converting 16 floz to ml">
</figure>

<p>By <a href="https://github.com/biati-digital">Biati Digital</a> and available in
the <a href="https://alfred.app/workflows/biatidigital/calculate-anything/">Alfred Gallery</a>.</p>
<h3 id="epoch-converter">
  Epoch converter
  <a href="#epoch-converter" class="heading-anchor" aria-label="Direct link to Epoch converter"><span aria-hidden="true">#</span></a>
</h3>
<p>There was a time when I kept looking at unix timestamps and needed to quickly
get when it was, so copying and then <code>ts &lt;paste&gt;</code> gave me when it was and
<code>ts</code> without anything will give you the current unix timestamp.</p>
<figure><img src="/img/2024/04/15/14-epoch-converter.png"
    alt="Epoch Converter showing the current unix timestamp">
</figure>

<p>By <a href="https://github.com/snooze92">Julien Lehuen</a> and available in
the <a href="https://alfred.app/workflows/julienlehuen/epoch-converter/">Alfred Gallery</a>.</p>
<h2 id="tying-it-up-and-how-i-figure-out-what-to-add">
  Tying it up and how I figure out what to add
  <a href="#tying-it-up-and-how-i-figure-out-what-to-add" class="heading-anchor" aria-label="Direct link to Tying it up and how I figure out what to add"><span aria-hidden="true">#</span></a>
</h2>
<p>I hope the above gives you some ideas or inspiration for what you could do
to improve your day-to-day computer usage. These are things that I&rsquo;ve pulled
together over years, and similarly I have a <a href="https://github.com/gaqzi/conf">bin/ folder</a> with other types of
automation I&rsquo;ve accrued, hehe. 😅</p>
<p>Now, how have I accrued these things? I generally try to reflect on the work
I do and the biggest help to it has been scheduling 30min every Friday to
reflect on the week. This session covers everything from updating my <a href="https://jvns.ca/blog/brag-documents/">brag
document</a> to reflecting on what work I felt I repeated a lot and whether
there&rsquo;s anything there to spend time on in the future. Pretty much,
what annoyed me when working and can I do something about it?</p>
<p>If I come up with something that I can&rsquo;t deal with right now then I&rsquo;ll
record it in Roam under <code>#hack-idea</code>, whether it&rsquo;s Friday or not, and when I
feel like hacking on something I&rsquo;ll look there. Because sometimes you just
need something quick to hack on. 🙂</p>
<p>I&rsquo;d love to hear what your favorite day-to-day automations are
on <a href="https://hachyderm.io/@gaqzi">Mastodon</a>!</p>
]]></content:encoded></item><item><title>How to make Docker resume downloads …download that final 100KB and not 200MB again</title><link>https://bjorn.now/blog/2024/02/01/how-to-make-docker-pull-resume-layer-downloads/?utm_medium=feed</link><pubDate>Thu, 01 Feb 2024 17:00:00 +0700</pubDate><guid>https://bjorn.now/blog/2024/02/01/how-to-make-docker-pull-resume-layer-downloads/</guid><description>In the Docker Desktop app you can go into Features in Development and check Use containerd for pulling and storing images.
Or you can do add the following to Docker’s daemon.json file:
{ "features": { "containerd-snapshotter": true } } This comes from a comment in a ticket for resuming downloads when they fail.</description><content:encoded><![CDATA[<p>In the Docker Desktop app you can go into <code>Features in Development</code> and check
<code>Use containerd for pulling and storing images</code>.</p>
<p>Or you can do add the following to Docker&rsquo;s <code>daemon.json</code> file:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;features&#34;</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;containerd-snapshotter&#34;</span><span class="o">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<p>This comes from <a href="https://github.com/docker/for-linux/issues/1187#issuecomment-1457279396">a comment</a> in a ticket for <a href="https://github.com/docker/for-linux/issues/1187">resuming downloads when they
fail.</a></p>
]]></content:encoded></item><item><title>Sticky blocks in Roam — floating along in your sidebar</title><link>https://bjorn.now/blog/2022/02/05/sticky-blocks-in-roam-research/?utm_medium=feed</link><pubDate>Sat, 05 Feb 2022 16:30:00 +0800</pubDate><guid>https://bjorn.now/blog/2022/02/05/sticky-blocks-in-roam-research/</guid><description>I use Roam Research as my primary way of thinking and keeping track of tasks. My current workflow has me creating a list of all tasks I want to get done, and then I drag them to either Done or some follow-up/waiting area for future action. So I only see things I can act on now.
My list of things to action can get quite long, and when I’m processing follow-up items, I would have a hard time dragging them to Done because it was too far away in the sidebar. So I wished I would make the Done float with me, so I could always have a quick way of pulling it there.</description><content:encoded><![CDATA[<p>I use <a href="https://roamresearch.com/">Roam Research</a> as my primary way of thinking and keeping track of
tasks. My current workflow has me creating a list of all tasks I want
to get done, and then I drag them to either <code>Done</code> or some follow-up/waiting
area for future action. So I only see things I can act on now.</p>
<p>My list of things to action can get quite long, and when I&rsquo;m processing
follow-up items, I would have a hard time dragging them to <code>Done</code> because it
was too far away in the sidebar. So I wished I would make the <code>Done</code> float
with me, so I could always have a quick way of pulling it there.</p>
<p>While looking at the custom styling in the <a href="https://twitter.com/RoamBookClub">Roam Book Club</a> graph, I found
the <code>#sticky</code> tag, which will make a block sticky over the children in the
block. So I took that CSS and modified it slightly to support making a
block float, so I could have it stay with me in the sidebar.</p>
<p>An example of how it looks in my sidebar before my new tags:
<figure><img src="/img/2022/02/05/01-bare-listing.png"
    alt="Unmodified list">
</figure>
</p>
<p>With the <code>#sticky</code> tag from the Roam Book Club, I can make for example
<code>Tasks</code> float when I&rsquo;m scrolling inside the tasks. This can be super helpful
for keeping track of where you are (they use it for daily chat pages) but
not quite what I need:
<figure><img src="/img/2022/02/05/02-sticky.png"
    alt="#sticky">
</figure>
</p>
<p>Introducing <code>#sticky-block</code>, which will make the block and all its children
float along at the top of the page, so I can always drag things there quickly:
<figure><img src="/img/2022/02/05/03-sticky-block.png"
    alt="#sticky-block">
</figure>
</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="c">/* To make blocks float, a constant in the sidebar */</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nc">roam-block-container</span><span class="o">[</span><span class="nt">data-page-links</span><span class="o">*=</span><span class="s2">&#34;sticky-block&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c">/* To make the a block float over its children. 
</span></span></span><span class="line"><span class="cl"><span class="c">   So you see which block you&#39;re currently inside.  
</span></span></span><span class="line"><span class="cl"><span class="c">   The weird \&#34; is because it would match both sticky and stick-block and   
</span></span></span><span class="line"><span class="cl"><span class="c">   double up the rendering. 
</span></span></span><span class="line"><span class="cl"><span class="c">   */</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="p">.</span><span class="nc">roam-block-container</span><span class="o">[</span><span class="nt">data-page-links</span><span class="o">*=</span><span class="s2">&#34;sticky\&#34;&#34;</span><span class="o">]</span> <span class="o">&gt;</span> <span class="p">.</span><span class="nc">rm-block-main</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">position</span><span class="p">:</span> <span class="kc">sticky</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">top</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">z-index</span><span class="p">:</span> <span class="mi">11</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">background</span><span class="p">:</span> <span class="kc">white</span><span class="p">;</span> <span class="c">/* Might have to be adjusted for your theme */</span>
</span></span><span class="line"><span class="cl">  <span class="k">padding-top</span><span class="p">:</span> <span class="mi">4</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">padding-bottom</span><span class="p">:</span> <span class="mi">2</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">margin-bottom</span><span class="p">:</span> <span class="mi">10</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">box-shadow</span><span class="p">:</span> 
</span></span><span class="line"><span class="cl">    <span class="mi">0</span> <span class="mi">1</span><span class="kt">px</span> <span class="mi">1</span><span class="kt">px</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mf">0.034</span><span class="p">),</span> 
</span></span><span class="line"><span class="cl">    <span class="mi">0</span> <span class="mi">1</span><span class="kt">px</span> <span class="mi">1</span><span class="kt">px</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mf">0.048</span><span class="p">),</span> 
</span></span><span class="line"><span class="cl">    <span class="mi">0</span> <span class="mf">12.5</span><span class="kt">px</span> <span class="mi">10</span><span class="kt">px</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mf">0.06</span><span class="p">),</span> 
</span></span><span class="line"><span class="cl">    <span class="mi">0</span> <span class="mf">12.3</span><span class="kt">px</span> <span class="mf">7.9</span><span class="kt">px</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mf">0.072</span><span class="p">),</span> 
</span></span><span class="line"><span class="cl">    <span class="mi">0</span> <span class="mf">7.8</span><span class="kt">px</span> <span class="mf">10.4</span><span class="kt">px</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mf">0.086</span><span class="p">),</span> 
</span></span><span class="line"><span class="cl">    <span class="mi">0</span> <span class="mi">5</span><span class="kt">px</span> <span class="mi">120</span><span class="kt">px</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mf">0.12</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">/* Hides the tag since it&#39;s not important in the output, and there is 
</span></span></span><span class="line"><span class="cl"><span class="c">special styling so it&#39;s clear that something extra is going on. */</span>
</span></span><span class="line"><span class="cl"><span class="nt">span</span><span class="p">.</span><span class="nc">rm-page-ref</span><span class="o">[</span><span class="nt">data-tag</span><span class="o">=</span><span class="s2">&#34;sticky-block&#34;</span><span class="o">],</span>
</span></span><span class="line"><span class="cl"><span class="nt">span</span><span class="p">.</span><span class="nc">rm-page-ref</span><span class="o">[</span><span class="nt">data-tag</span><span class="o">=</span><span class="s2">&#34;sticky&#34;</span><span class="o">]</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">display</span><span class="p">:</span> <span class="kc">none</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<p><strong>Note:</strong> This CSS currently does not support having both <code>#sticky-block</code> and
<code>#sticky</code> for the same page, they will overlap each other, and the second
element will block the first. I don&rsquo;t currently need this, so I haven&rsquo;t spent
the time. :)</p>
]]></content:encoded></item><item><title>How to find your Apple Watch when you've misplaced it — How I ended up being a sonar bear</title><link>https://bjorn.now/blog/2015/11/14/how-to-ping-a-lost-apple-watch/?utm_medium=feed</link><pubDate>Sat, 14 Nov 2015 21:03:42 +0800</pubDate><guid>https://bjorn.now/blog/2015/11/14/how-to-ping-a-lost-apple-watch/</guid><description>I just misplaced my Apple Watch in my apartment. Walking around looking for it, and getting frustrated, I was thinking, “There ought to be a way to make it make some sound, then I can play Marco Polo with the watch”. But my Google-fu failed me: no obvious way to “ping my watch”. There’s functionality for pinging the phone from the watch though.
But there’s a way to make some noise: testing out the alert volume in the companion app on the phone.</description><content:encoded><![CDATA[<p>I just misplaced my Apple Watch in my apartment. Walking around looking for it,
and getting frustrated, I was thinking, &ldquo;There ought to be a way to make it make
some sound, then I can play <a href="https://en.wikipedia.org/wiki/Marco_Polo_(game)">Marco Polo</a> with the watch&rdquo;. But my Google-fu
failed me: no obvious way to &ldquo;ping my watch&rdquo;. There&rsquo;s functionality for
<a href="https://www.youtube.com/watch?v=KYf4q4y7dM4">pinging the phone</a> from the watch though.</p>
<p>But there&rsquo;s a way to make some noise: testing out the alert volume in the
companion app on the phone.</p>
<h2 id="steps">
  Steps
  <a href="#steps" class="heading-anchor" aria-label="Direct link to Steps"><span aria-hidden="true">#</span></a>
</h2>
<ol>
<li>
<p>Verify that your Watch is paired:</p>
<ol>
<li>Settings</li>
<li>Bluetooth</li>
<li>My Devices should show your Watch:
<figure><img src="/img/2015/11/bluetooth-settings.png"
          alt="Bluetooth settings on an iPhone">
      </figure>
</li>
</ol>
</li>
<li>
<p>Open the Apple Watch app:</p>
<ol>
<li>Sounds &amp; Haptics</li>
<li>Drag the slider for alert volume to max</li>
<li>Tap the knob on the slider.<br>
The watch will make a sound every time you tap the knob.
<figure><img src="/img/2015/11/alert-volume-settings.png"
          alt="Alert volume settings">
      </figure>
</li>
</ol>
</li>
<li>
<p>Don&rsquo;t cry yourself to sleep over losing an expensive toy in the mess that is
your own apartment.</p>
</li>
</ol>
]]></content:encoded></item><item><title>Using multiple versions of Python on Snap CI — Why just play with one snake?</title><link>https://bjorn.now/blog/2015/10/27/testing-against-many-versions-of-python-on-snap-ci/?utm_medium=feed</link><pubDate>Tue, 27 Oct 2015 08:00:00 +0800</pubDate><guid>https://bjorn.now/blog/2015/10/27/testing-against-many-versions-of-python-on-snap-ci/</guid><description>I’m running an open source project called gocd-cli which is, well, a command line interface for Go continuous delivery. The intention is to make it easier to handle common tasks around Go. But this post isn’t really about that.
This project is supposed to be as portable as I can make it, becuse the original need I felt for it was born on RHEL6. Which is blessed with Python 2.6 by default. And we should definitely be looking to the future, meaning supporting Python 3, and I luckily got a pull request for just that. Since I couldn’t find any free hosted Go around I went for the next best thing, Snap CI, which is also built by ThoughtWorks.</description><content:encoded><![CDATA[<p>I&rsquo;m running an open source project called <a href="https://github.com/gaqzi/gocd-cli">gocd-cli</a> which is, well, a command
line interface for <a href="http://www.go.cd/">Go continuous delivery</a>. The intention is to make it
easier to handle common tasks around Go. But this post isn&rsquo;t really about that.</p>
<p>This project is supposed to be as portable as I can make it, becuse the original
need I felt for it was born on RHEL6. Which is <em>blessed</em> with Python 2.6 by
default. And we should definitely be looking to the future, meaning supporting
Python 3, and I luckily got a <a href="https://github.com/gaqzi/py-gocd/pull/6">pull request</a> for just that. Since I couldn&rsquo;t
find any free hosted Go around I went for the next best thing, <a href="https://snap-ci.com/">Snap CI</a>,
which is also built by <a href="https://en.wikipedia.org/wiki/ThoughtWorks">ThoughtWorks</a>.</p>
<p>Easy to get going and works well, it&rsquo;s not Go, but it&rsquo;s definitely the easy
starter drug for people that thinks <a href="https://en.wikipedia.org/wiki/Jenkins_(software)">Jenkins</a> or <a href="https://en.wikipedia.org/wiki/Travis_CI">Travis</a> is good enough.</p>
<h2 id="the-solution">
  The solution
  <a href="#the-solution" class="heading-anchor" aria-label="Direct link to The solution"><span aria-hidden="true">#</span></a>
</h2>
<p>Snap supports all the versions of Python that I want by default, but they
weren&rsquo;t all being made available at the same time. Python 2.6 and 2.7
were always there, but only one version of Python 3 at a time.</p>
<p>Luckily logging in to the <a href="https://blog.snap-ci.com/blog/2014/08/11/introducing-snap-shell/">snap-shell</a> I found that all Python versions were
available in <code>/opt/local/python/&lt;version&gt;</code>.</p>
<p>A quick change to the Snap config, to add these to my <code>$PATH</code>, and <a href="https://tox.readthedocs.org/en/latest/">tox</a> ran
through without any problems.</p>
<figure><img src="/img/2015/10/snap-console.png"
    alt="New Snap CI config">
</figure>

<p>What you&rsquo;ll need to add to your command box. <strong>Note</strong> it didn&rsquo;t work for me when
I added it to the environment variable field, I&rsquo;m guessing it&rsquo;s because the <code>$PATH</code>
variable doesn&rsquo;t get interpolated correctly.</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$PATH</span><span class="s2">:/opt/local/python/3.3.5/bin:/opt/local/python/3.4.0/bin:/opt/local/python/3.5.0/bin&#34;</span></span></span></code></pre></div>
</div>
<p>And look, my tests are passing!</p>
<p><a href="https://snap-ci.com/gaqzi/py-gocd/branch/master"><figure><img src="/img/2015/10/snap-success.png"
    alt="Passing tests">
</figure>
</a></p>
]]></content:encoded></item><item><title>Python and its included batteries</title><link>https://bjorn.now/blog/2013/01/18/python-and-its-included-batteries/?utm_medium=feed</link><pubDate>Fri, 18 Jan 2013 16:22:32 +0000</pubDate><guid>https://bjorn.now/blog/2013/01/18/python-and-its-included-batteries/</guid><description>I’ve for almost a year now been using Python as my day to day language as it’s what is used at my job. I’m starting to come around to really liking most parts of Python now, although there is definitely parts that are way more clunky than I would expect from a language so popular. And especially given how much talk there’s about the included batteries and to a small extent how there seems to bit of dickishness involved with hard core Python people. For an example of the latter just:</description><content:encoded><![CDATA[<p>I&rsquo;ve for almost a year now been using <a href="http://en.wikipedia.org/wiki/Python_(programming_language)">Python</a> as my day to day language as it&rsquo;s
what is used at my job. I&rsquo;m starting to come around to really liking most parts
of Python now, although there is definitely parts that are way more clunky than
I would expect from a language so popular. And especially given how much talk
there&rsquo;s about the <em><a href="http://www.python.org/about/">included batteries</a></em> and to a small extent how there seems
to bit of dickishness involved with hard core Python people. For an example of
the latter just:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ python  
</span></span><span class="line"><span class="cl">&gt;&gt;&gt; <span class="nb">exit</span>  
</span></span><span class="line"><span class="cl">Use exit<span class="o">()</span> or Ctrl-D <span class="o">(</span>i.e. EOF<span class="o">)</span> to exit</span></span></code></pre></div>
</div>
<p>Clearly the interpreter known what I want to achieve, and at the same time it&rsquo;s
programmed to give of an error message telling me I was doing it wrong. And for
it to work I need to say, &ldquo;pretty please&rdquo;. What annoys me with this is just the
simple fact that someone figured out people were having a problem with this, so
they added in a message instead of just doing what is expected. You just don&rsquo;t
behave in that way in everyday life, you&rsquo;d get slapped.</p>
<h2 id="standard-library">
  Standard library
  <a href="#standard-library" class="heading-anchor" aria-label="Direct link to Standard library"><span aria-hidden="true">#</span></a>
</h2>
<p>Time handling in Python is atrocious, do get it working halfway decently you
need to install at least two external libraries. <a href="http://pytz.sourceforge.net/">pytz</a> and <a href="http://labix.org/python-dateutil">python-dateutil</a>,
and while I can understand why pytz might be a good candidate for being external
(for allowing a higher pace of updates since timezone/<a href="http://en.wikipedia.org/wiki/DST">DST</a> changes happens
every so often in different parts of the world).</p>
<p>Today I just wanted to do a diff between two dates and see the years between
them, so I figured I would use <a href="http://docs.python.org/2/library/datetime.html">timedelta</a> since it sounds like something that
should be part of that. Oh, really, it&rsquo;s not? I can diff weeks but not months or
years? So I ended up using dateutil&rsquo;s <a href="http://labix.org/python-dateutil#head-ba5ffd4df8111d1b83fc194b97ebecf837add454">relativedelta</a> instead.</p>
<p>So far the worst way I&rsquo;ve been bitten was with the built-in e-mail library, this
ended up with me sending a copy of the e-mail addresses of 150 customers by
mistake.</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">smtplib</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">email.mime.text</span> <span class="kn">import</span> <span class="n">MIMEText</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="n">msg</span> <span class="o">=</span> <span class="n">MIMEText</span><span class="p">(</span><span class="s1">&#39;Ho ho ho, merry christmas&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">msg</span><span class="p">[</span><span class="s1">&#39;Subject&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;You&#39;re wished something special!&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">msg</span><span class="p">[</span><span class="s1">&#39;From&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;noreply@otherdomain.tld&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">msg</span><span class="p">[</span><span class="s1">&#39;To&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;user-1@domain.tld&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">msg</span><span class="p">[</span><span class="s1">&#39;To&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;user-2@domain.tld&#39;</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="nb">print</span> <span class="n">msg</span><span class="o">.</span><span class="n">as_string</span><span class="p">()</span></span></span></code></pre></div>
</div>
<p>Output:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-plain" data-lang="plain"><span class="line"><span class="cl">Content-Type: text/plain; charset=&#34;us-ascii&#34;
</span></span><span class="line"><span class="cl">MIME-Version: 1.0
</span></span><span class="line"><span class="cl">Content-Transfer-Encoding: 7bit
</span></span><span class="line"><span class="cl">Subject: You&#39;re wished something special!
</span></span><span class="line"><span class="cl">From: noreply@otherdomain.tld
</span></span><span class="line"><span class="cl">To: user-1@domain.tld
</span></span><span class="line"><span class="cl">To: user-2@domain.tld
</span></span><span class="line"><span class="cl">   
</span></span><span class="line"><span class="cl">Ho ho ho, merry christmas</span></span></code></pre></div>
</div>
<p>Because appending when doing an assignment is just what you&rsquo;d expect. This from
the same people that needs to tell me I can&rsquo;t type <code>exit</code> but I need to type
<code>exit()</code>.</p>
<p>Another thing I wanted to use was a windowing function when iterating over a
list, instead of having it implemented in <a href="http://docs.python.org/2.7/library/itertools.html">itertools</a> the documentation for
itertools explains how I can
<a href="http://docs.python.org/release/2.3.5/lib/itertools-example.html">implement my own</a>.
Why!? If you&rsquo;re taking the time to show of an implementation why should I have
to copy that piece of code into my own codebase? What about <a href="http://www.python.org/dev/peps/pep-0020/">The Zen of Python</a>:</p>
<blockquote>
<p>There should be one&ndash; and preferably only one &ndash;obvious way to do it.</p>
</blockquote>
<h3 id="pep8">
  PEP8
  <a href="#pep8" class="heading-anchor" aria-label="Direct link to PEP8"><span aria-hidden="true">#</span></a>
</h3>
<p>I really like <a href="http://www.python.org/dev/peps/pep-0008/">PEP8</a>, I don&rsquo;t agree with all parts of it but I can change. But
I&rsquo;m really looking forward to when the standard library finally is PEP8
compliant.</p>
<h2 id="documentation">
  Documentation
  <a href="#documentation" class="heading-anchor" aria-label="Direct link to Documentation"><span aria-hidden="true">#</span></a>
</h2>
<p>Which brings me to documentation. For the longest time I couldn&rsquo;t understand why
I had such problems understanding the Python documentation, whether it was the
standard library or any third party library. When I was coding <a href="http://en.wikipedia.org/wiki/Ruby_(programming_language)">Ruby</a> I never
felt this stupid when reading documentation. (But I&rsquo;ve been told I&rsquo;m excused
from feeling stupid when reading the <a href="http://docs.sqlalchemy.org/">SQLAlchemy documentation</a>, apparently it&rsquo;s
not just me there.)</p>
<p>But I think I&rsquo;ve figured it why I find the documentation so hard to understand
now. When I&rsquo;m reading documentation for Ruby I generally have the source code
just one mouse click away. So when I wasn&rsquo;t understanding what the documentation
was saying I just flipped on the source code and I could figure it out.</p>
<figure><img src="/img/2013/Screenshot_2013-01-19_00_30.png"
    alt="Ruby documentation source example">
</figure>

<p>With most Python libraries I&rsquo;ve had to do something similar to this:</p>
<ul>
<li>Dig through to wherever the source is saved</li>
<li>Find the correct version for the documentation I&rsquo;m reading(missed version of source I&rsquo;m reading more than once)</li>
<li>Find exactly where this thing I want to read about is is defined.</li>
<li>Start reading the source</li>
<li>???</li>
<li>Enlightenment.</li>
</ul>
<p>It&rsquo;s a general &ldquo;truth&rdquo; that documentation in general sucks, so it&rsquo;s just better
read the source. But somewhere along the way I stopped doing that, probably
around the same time I started using Rails and was amazed at how good the
documentation was. And without thinking about it I was also already reading the
source code, kind of, by having it readily available with the documentation.</p>
<p>My recommendation is for all of you out there using <a href="http://sphinx-doc.org/">Sphinx</a> for your
documentation needs, please enable
<a href="http://sphinx-doc.org/latest/ext/viewcode.html">sphinx.ext.viewcode</a> to output
the source with your documentation.</p>
<p>The other thing I would like to say is the obvious thing, reading the source is
great. I really started to enjoy using Django when I decided to just use the
documentation to find whatever something was called and then use the source code
to figure out how things work. Source code is almost like a painting, a line of
code can say more than a thousand words. ;)</p>
<h2 id="wrapping-up">
  Wrapping up
  <a href="#wrapping-up" class="heading-anchor" aria-label="Direct link to Wrapping up"><span aria-hidden="true">#</span></a>
</h2>
<p>I&rsquo;m not the first one to feel dismayed about Python, <a href="http://zedshaw.com/">Zed Shaw</a> has a great post
called
<a href="http://zedshaw.com/essays/curing_pythons_neglect.html">Curing Python&rsquo;s Neglect</a>
that is well worth a read. (He mentions Lamson 0.9 in the post and that was
released in 2009. As far as I can tell most of the things in his post is still a
problem.)</p>
<p>As I understand a lot of making Python more streamlined is being put into Python
3, that just hasn&rsquo;t been adopted by the majority of developers yet.</p>
<p>We Ruby people had the same thing with 1.9, and I noticed people really starting
to switch about the same time as <a href="https://rvm.io/">RVM</a> started to be used. All of a sudden there
was a really easy way to test things with any possible combination of Ruby you
wanted.</p>
<p>All around I&rsquo;m really enjoying working with Python, again (Python was the
language I used to replace PHP/Perl way back when, then Python was replaced by
Ruby). I used to really dislike having to import every single line of code I
use, but since I&rsquo;m a sucker for having things overly clear I&rsquo;ve gotten round to
it now. For some reason I never had an issue with the white spaces, I want the
code I read to be consistent. And that&rsquo;s one way of trying to ensure that.</p>
<p>Now that I&rsquo;ve done the virtual variant of screaming out into the night I&rsquo;ll go
and try to be productive instead.</p>
<figure><img src="/img/2013/classy-python.png"
    alt="Classy Python">
</figure>

]]></content:encoded></item><item><title>Keeping your Varnish farms configuration in sync</title><link>https://bjorn.now/blog/2011/01/07/keeping-your-varnish-farm-in-sync/?utm_medium=feed</link><pubDate>Fri, 07 Jan 2011 01:21:46 +0000</pubDate><guid>https://bjorn.now/blog/2011/01/07/keeping-your-varnish-farm-in-sync/</guid><description>I spend some of my free time helping out with server administration and programming for the Swedish Pirate Party, Piratpartiet (look ma, X-Varnish), and we use Varnish for our caches. At the moment it’s just used on some of our projects, but we’re going to migrate most of our projects to be behind our Varnish caches.
The thing is though that we got two hosts running the exact same configuration, and we’d like to keep those hosts in sync. A quick search on Google gave me nothing for syncing varnish configuration over several hosts, or a farm as it were. So I took matters into my own hands and wrote varnishsync, a little bash script that uses rsync and ssh to sync the configuration folder and then to load and use the new configuration.</description><content:encoded><![CDATA[<p>I spend some of my free time helping out with server administration and
programming for the <a href="https://secure.wikimedia.org/wikipedia/en/wiki/Piratpartiet">Swedish Pirate Party</a>, <a href="http://www.piratpartiet.se/">Piratpartiet</a> (<em>look ma</em>,
X-Varnish), and we use <a href="http://www.varnish-cache.org/">Varnish</a> for our caches. At the moment it&rsquo;s just used on
some of our projects, but we&rsquo;re going to migrate most of our projects to be
behind our Varnish caches.</p>
<p>The thing is though that we got two hosts running the exact same configuration,
and we&rsquo;d like to keep those hosts in sync. A quick search on Google gave me
nothing for syncing varnish configuration over several hosts, or a farm as it
were. So I took matters into my own hands and wrote <a href="https://github.com/gaqzi/varnishsync">varnishsync</a>, a little bash
script that uses rsync and ssh to sync the configuration folder and then to load
and use the new configuration.</p>
<p>Please have a look at the <a href="https://github.com/gaqzi/varnishsync">Github project</a> for the latest version
and usage, and if you&rsquo;ve any questions or suggestions drop a line here or on
Github.</p>
]]></content:encoded></item><item><title>How to: Setting up a Wikileaks mirror</title><link>https://bjorn.now/blog/2010/12/05/howto-setting-up-a-wikileaks-mirror/?utm_medium=feed</link><pubDate>Sun, 05 Dec 2010 21:11:18 +0000</pubDate><guid>https://bjorn.now/blog/2010/12/05/howto-setting-up-a-wikileaks-mirror/</guid><description>Yesterday I wrote a quick step-by-step guide for installing apache and setting your system up for Wikileaks mass-mirror project in Swedish and I’ve had requests for it to be translated into English.
This guide assumes you’re running Ubuntu or a Debian based system.
I’ve made a script that does all the steps below, feel free to use it!
The script: http://sanitarium.se/files/wikileaks-mirror.sh
Usage: wget http://sanitarium.se/files/wikileaks-mirror.sh &amp;&amp; chmod +x wikileaks-mirror.sh &amp;&amp; ./wikileaks-mirror.sh
Installing Apache and adding the wikileaks user # # is the prompt as root:</description><content:encoded><![CDATA[<p>Yesterday I wrote a quick step-by-step guide for installing apache and setting
your system up for <a href="http://213.251.145.96/Mass-mirroring-Wikileaks.html">Wikileaks mass-mirror project</a> in Swedish and
I&rsquo;ve had requests for it to be translated into English.</p>
<p>This guide assumes you&rsquo;re running Ubuntu or a Debian based system.</p>
<p>I&rsquo;ve made a script that does all the steps below, feel free to use it!<br>
The script: <a href="http://sanitarium.se/files/wikileaks-mirror.sh">http://sanitarium.se/files/wikileaks-mirror.sh</a><br>
Usage: <code>wget http://sanitarium.se/files/wikileaks-mirror.sh &amp;&amp; chmod +x wikileaks-mirror.sh &amp;&amp; ./wikileaks-mirror.sh</code></p>
<h1 id="installing-apache-and-adding-the-wikileaks-user">
  Installing Apache and adding the wikileaks user
  <a href="#installing-apache-and-adding-the-wikileaks-user" class="heading-anchor" aria-label="Direct link to Installing Apache and adding the wikileaks user"><span aria-hidden="true">#</span></a>
</h1>
<p><code>#</code> is the prompt as root:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">youruser$ sudo -s  </span></span></code></pre></div>
</div>
<p>Install apache if it&rsquo;s not installed</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># apt-get install apache2  </span></span></span></code></pre></div>
</div>
<p>Add a wikileaks user, write down where the home folder is created.</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># adduser --disabled-password wikileaks  </span>
</span></span><span class="line"><span class="cl"><span class="c1"># su wikileaks  </span>
</span></span><span class="line"><span class="cl">wikileaks$ mkdir ~/.ssh ~/www  
</span></span><span class="line"><span class="cl">wikileaks$ chmod <span class="m">0700</span> ~/.ssh  
</span></span><span class="line"><span class="cl">wikileaks$ wget http://213.251.145.96/IMG/id_rsa.pub -O ~/.ssh/authorized_keys  
</span></span><span class="line"><span class="cl">wikileaks$ <span class="nb">exit</span>  </span></span></code></pre></div>
</div>
<p>Time to add the apache site for wikileaks</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># cd /etc/apache2/sites-available/   </span>
</span></span><span class="line"><span class="cl"><span class="c1"># wget http://sanitarium.se/files/wikileaks   </span></span></span></code></pre></div>
</div>
<p>Open the file <code>wikileaks</code> with an editor and change the path to the wikileaks users home folder if it&rsquo;s not <code>/home/wikileaks</code>.<br>
This file assumes you want to host wikileaks.org, if you want to host wikileaks.yourdomain.com you&rsquo;ll need to set <code>ServerName</code> accordingly and if necessary <code>ServerAlias</code>, ServerAlias is optional and you can remove that row if you don&rsquo;t use it.<br>
When that is done you just need to activate the site in apache:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># a2ensite wikileaks  </span></span></span></code></pre></div>
</div>
<p>And reload apache so it knows that the new site has been added:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># /etc/init.d/apache2 reload  </span></span></span></code></pre></div>
</div>
<p>The <code>wikileaks</code> file downloaded from this server looks like this:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&lt;VirtualHost *:80&gt;
</span></span><span class="line"><span class="cl">    DocumentRoot /home/wikileaks/www
</span></span><span class="line"><span class="cl">    ServerName wikileaks.org
</span></span><span class="line"><span class="cl">    ServerAlias www.wikileaks.org
</span></span><span class="line"><span class="cl">    ErrorLog /dev/null
</span></span><span class="line"><span class="cl">    CustomLog /dev/null common
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    &lt;Directory /home/wikileaks/www&gt;
</span></span><span class="line"><span class="cl">        AllowOverride None
</span></span><span class="line"><span class="cl">    &lt;/Directory&gt;
</span></span><span class="line"><span class="cl">&lt;/VirtualHost&gt;</span></span></code></pre></div>
</div>
<h1 id="now-tell-wikileaks-about-your-mirror">
  Now tell Wikileaks about your mirror
  <a href="#now-tell-wikileaks-about-your-mirror" class="heading-anchor" aria-label="Direct link to Now tell Wikileaks about your mirror"><span aria-hidden="true">#</span></a>
</h1>
<p>Go to the <a href="http://213.251.145.96/Mass-mirroring-Wikileaks.html">form</a> and fill it as the picture, set the IP-address of your server in the IP-field:<br>
<figure><img src="/img/2010/installning-hos-wikileaks.png"
    alt="Wikileaks mirror settings">
</figure>
</p>
<p>If you&rsquo;re having any troubles don&rsquo;t hesitate to ask in the comments!</p>
]]></content:encoded></item><item><title>How to: Sätta upp en Wikileaksspegling</title><link>https://bjorn.now/blog/2010/12/04/howto-satta-upp-en-wikileaksspegling/?utm_medium=feed</link><pubDate>Sat, 04 Dec 2010 21:27:26 +0000</pubDate><guid>https://bjorn.now/blog/2010/12/04/howto-satta-upp-en-wikileaksspegling/</guid><description>A translation of this post is available in English.
En kommentar i Rick Falkvinges inlägg Fullt krig om informationen så efterfrågades en snabbguide för att dra igång en Wikileaksspegling i ett vanligt Linuxssystem.
Jag utgår från Ubuntu eftersom det är vanligt och vad jag själv använder.
Uppdatering 2010-12-05: Jag har gjort ett script som utför alla stegen nedan, använd gärna det!
Filen: http://sanitarium.se/files/wikileaks-mirror.sh
Användning: wget http://sanitarium.se/files/wikileaks-mirror.sh &amp;&amp; chmod +x wikileaks-mirror.sh &amp;&amp; ./wikileaks-mirror.sh
Installera Apache # # är prompten som root:</description><content:encoded><![CDATA[<p><a href="/blog/2010/12/05/howto-setting-up-a-wikileaks-mirror/">A translation of this post is available in English.</a></p>
<p>En <a href="http://rickfalkvinge.se/2010/12/04/fullt-krig-om-informationen/#comment-57303">kommentar</a> i Rick Falkvinges inlägg <a href="http://rickfalkvinge.se/2010/12/04/fullt-krig-om-informationen/">Fullt krig om informationen</a>
så efterfrågades en snabbguide för att dra igång en Wikileaksspegling i ett
vanligt Linuxssystem.</p>
<p>Jag utgår från Ubuntu eftersom det är vanligt och vad jag själv använder.</p>
<p><strong>Uppdatering 2010-12-05:</strong> Jag har gjort ett script som utför alla stegen nedan, använd gärna det!<br>
Filen: <a href="http://sanitarium.se/files/wikileaks-mirror.sh">http://sanitarium.se/files/wikileaks-mirror.sh</a><br>
Användning: <code>wget http://sanitarium.se/files/wikileaks-mirror.sh &amp;&amp; chmod +x wikileaks-mirror.sh &amp;&amp; ./wikileaks-mirror.sh</code></p>
<h1 id="installera-apache">
  Installera Apache
  <a href="#installera-apache" class="heading-anchor" aria-label="Direct link to Installera Apache"><span aria-hidden="true">#</span></a>
</h1>
<p><code>#</code> är prompten som root:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dinanvändare$ sudo -s  </span></span></code></pre></div>
</div>
<p>Installera apache om det inte är installerat</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># apt-get install apache2  </span></span></span></code></pre></div>
</div>
<p>Lägg till en användare för Wikileak och anteckna var wikileaks hemmap skapas!</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># adduser --disabled-password wikileaks  </span>
</span></span><span class="line"><span class="cl"><span class="c1"># su wikileaks  </span>
</span></span><span class="line"><span class="cl">wikileaks$ mkdir ~/.ssh ~/www  
</span></span><span class="line"><span class="cl">wikileaks$ chmod <span class="m">0700</span> ~/.ssh  
</span></span><span class="line"><span class="cl">wikileaks$ wget http://213.251.145.96/IMG/id_rsa.pub -O ~/.ssh/authorized_keys  
</span></span><span class="line"><span class="cl">wikileaks$ <span class="nb">exit</span>  </span></span></code></pre></div>
</div>
<p>Nu är det dags att lägga till siten för wikileaks</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># cd /etc/apache2/sites-available/   </span>
</span></span><span class="line"><span class="cl"><span class="c1"># wget http://sanitarium.se/files/wikileaks   </span></span></span></code></pre></div>
</div>
<p>Öppna filen <code>wikileaks</code> med en editor och ändra sökvägen till användaren wikileaks hemmapp om det är något annat än <code>/home/wikileaks</code>.<br>
Den här filen förutsätter att du vill hosta wikileaks.org, om du vill hosta wikileaks.dindomän.se eller något sådant så får du ändra ServerName och eventuellt ServerAlias.<br>
När det är klart är det bara att aktivera siten för apache:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># a2ensite wikileaks  </span></span></span></code></pre></div>
</div>
<p>Och till sist ladda om apache så det är redo att ta emot information:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># /etc/init.d/apache2 reload  </span></span></span></code></pre></div>
</div>
<p>Innehållet i filen <code>wikileaks</code> som du tankar från den här servern är:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&lt;VirtualHost *:80&gt;
</span></span><span class="line"><span class="cl">    DocumentRoot /home/wikileaks/www
</span></span><span class="line"><span class="cl">    ServerName wikileaks.org
</span></span><span class="line"><span class="cl">    ServerAlias www.wikileaks.org
</span></span><span class="line"><span class="cl">    ErrorLog /dev/null
</span></span><span class="line"><span class="cl">    CustomLog /dev/null common
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    &lt;Directory /home/wikileaks/www&gt;
</span></span><span class="line"><span class="cl">        AllowOverride None
</span></span><span class="line"><span class="cl">    &lt;/Directory&gt;
</span></span><span class="line"><span class="cl">&lt;/VirtualHost&gt;</span></span></code></pre></div>
</div>
<h1 id="hur-du-säger-till-wikileaks-att-allt-är-klart">
  Hur du säger till Wikileaks att allt är klart
  <a href="#hur-du-s%c3%a4ger-till-wikileaks-att-allt-%c3%a4r-klart" class="heading-anchor" aria-label="Direct link to Hur du säger till Wikileaks att allt är klart"><span aria-hidden="true">#</span></a>
</h1>
<p>Surfa in på <a href="http://213.251.145.96/Mass-mirroring-Wikileaks.html">formuläret</a> och fyll i det enligt nedan, byt ut ip-adress mot IP:t för din server:<br>
<figure><img src="/img/2010/installning-hos-wikileaks.png"
    alt="Wikileaks mirror settings">
</figure>
</p>
<p>Om det är några oklarheter eller funderingar så fråga på i kommentarsfältet
nedan så ska jag försöka hjälpa till att reda ut!</p>
<p><strong>Uppdatering:</strong> <a href="http://twitter.com/rouhivuori">Henrik Rouhivuori</a>
upplyste om att <code>aptitude</code> inte finns i Ubuntu 10.10 så jag ändrade till <code>apt-get</code>
istället. Övertydligt att jag fortfarande hänger på 10.04. :)</p>
<p><strong>Uppdatering 2:</strong>
Henrik Holst
berättade att du kan välja att inaktivera lösenordsinloggning när du skapar
nya användare, så nu görs det istället för att rekommendera ett slumpmässigt
långt lösenord!</p>
<p><strong>Uppdatering 3:</strong>
David Vrensk
påminner om att <code>sshd</code> är lite petig på att det bara är användaren som ska ha
rättigheter till <code>.ssh</code>-mappen.</p>
<p><strong>Uppdatering 4:</strong> Av bara farten så hade jag missat att lägga till att man
måste skapa mappen <code>www</code> som wikileaks-filen pekar på, samt <code>common</code> efter
loggraden. Tack till <a href="http://harfagre.wordpress.com/">Gustav Wetter</a> som jobbet igenom hela guiden!</p>
]]></content:encoded></item><item><title>How to disable bold fonts in xterm — Why sometimes repeating yourself really does get the message through</title><link>https://bjorn.now/blog/2010/02/21/how-to-disable-bold-fonts-in-xterm/?utm_medium=feed</link><pubDate>Sun, 21 Feb 2010 22:36:19 +0000</pubDate><guid>https://bjorn.now/blog/2010/02/21/how-to-disable-bold-fonts-in-xterm/</guid><description>I’ve so far in all my travels never found a terminal I like better than xterm, maybe urxvt, but I’ve had a problem disabling bold fonts for a very long time. Update: Seems this is a known bug for xterm.
There are a lot of X resource configuration options that makes you think you can disable bold fonts, but alas, I’ve had no luck with any of them. But if you set the same font for both normal and bold fonts it’ll work! This little gem of knowledge I found as an off-hand remark on a configuration page by Emil Mikulic, thank you!</description><content:encoded><![CDATA[<p>I&rsquo;ve so far in all my travels never found a terminal I like better than <a href="http://en.wikipedia.org/wiki/Xterm">xterm</a>, maybe <a href="http://en.wikipedia.org/wiki/Rxvt-unicode">urxvt</a>, but I&rsquo;ve had a problem disabling bold fonts for a very long time. <strong>Update:</strong> Seems this is a <a href="http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=347790">known bug for xterm</a>.</p>
<p>There are a lot of X resource configuration options that makes you think you can disable bold fonts, but alas, I&rsquo;ve had no luck with any of them. But if you set <strong>the same font</strong> for both normal and bold fonts it&rsquo;ll work! This little gem of knowledge I found as an off-hand remark on a <a href="http://dmr.ath.cx/notes/xterm.html">configuration page</a> by <a href="http://dmr.ath.cx/">Emil Mikulic</a>, thank you!</p>
<p>I like the normal <code>fixed</code> font, I&rsquo;ve tried many terminal fonts but I&rsquo;ve always gotten back to it. So I simply tucked:</p>
<pre><code>xterm*font: fixed
xterm*boldFont: fixed
</code></pre>
<p>Into my <code>~/.Xresources</code> and afterwards ran <code>xrdb -load ~/.Xresources</code> and started up a new xterm loaded with awesomeness!</p>
<figure><img src="/img/2010/xterms.png"
    alt="xterm with bold font and xterm without bold font">
</figure>

<p>The before and after shot.</p>
]]></content:encoded></item></channel></rss>