<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.penkin.me/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.penkin.me/" rel="alternate" type="text/html" /><updated>2026-02-12T21:13:54+00:00</updated><id>https://www.penkin.me/feed.xml</id><title type="html">Christopher Penkin</title><subtitle>Christopher shares projects, lessons, and thoughts on clean code, UX design, and creative tinkering—both digital and analog.</subtitle><author><name>Christopher Penkin</name></author><entry><title type="html">I Built a Chrome Extension for Generating SARS Test Data</title><link href="https://www.penkin.me/development/tools/testing/chrome-extension/2026/02/12/sars-testing-tools-chrome-extension.html" rel="alternate" type="text/html" title="I Built a Chrome Extension for Generating SARS Test Data" /><published>2026-02-12T00:00:00+00:00</published><updated>2026-02-12T00:00:00+00:00</updated><id>https://www.penkin.me/development/tools/testing/chrome-extension/2026/02/12/sars-testing-tools-chrome-extension</id><content type="html" xml:base="https://www.penkin.me/development/tools/testing/chrome-extension/2026/02/12/sars-testing-tools-chrome-extension.html"><![CDATA[<p>If you’ve ever tested software that deals with South African tax data, you know the pain. You need a valid-looking ID number. Or a PAYE reference. Or an income tax number. And it can’t just be random digits; these numbers have specific formats and validation algorithms. Get the check digit wrong and your form rejects it before you can even say <strong>BAZINGA</strong>!</p>

<figure>
    <img src="https://www.penkin.me/assets/img/bazinga.gif" alt="Sheldon from The Big Bang Theory saying Bazinga." />
    <figcaption>Fig1. - Sheldon from The Big Bang Theory saying Bazinga</figcaption>
</figure>

<p>There are some extensions that allow you to generate a valid South African ID number but I needed all the numbers, so I built a Chrome extension that does it all for me.</p>

<h2 id="the-problem">The Problem</h2>

<p>In a <a href="https://www.penkin.me/ai/development/tools/productivity/2026/02/09/claude-workflow.html">previous post</a>, I mentioned working on validation for identification fields; PAYE references, SDL references, UIF references. That work made it impossible to quickly demo or use those screens. Luckily they are not all required fields but not required does not mean never used.</p>

<p>Every time you need to test an employee form, a company registration screen, or a tax submission flow, you need structurally valid reference numbers. Not real ones; just ones that pass the validation algorithms.</p>

<h2 id="what-it-does">What It Does</h2>

<p><a href="https://chromewebstore.google.com/detail/sars-testing-tools/lkgdflhcanokafddhpmmfnghpdhhgcka">SARS Testing Tools</a> is a simple Chrome extension that generates six types of South African reference numbers:</p>

<ul>
  <li><strong>SA ID Numbers</strong> — 13 digits with Luhn check digit validation. You can filter by date range, gender, and citizenship status.</li>
  <li><strong>Income Tax Numbers</strong> — 10 digits using the SARS Modulus 10 algorithm.</li>
  <li><strong>Company Registration Numbers</strong> — In the <code class="language-plaintext highlighter-rouge">YYYY/NNNNNN/XX</code> format with 14 different company type codes.</li>
  <li><strong>PAYE References</strong> — 10 digits with SARS Modulus 10 validation.</li>
  <li><strong>UIF References</strong> — Prefixed with <code class="language-plaintext highlighter-rouge">U</code>, 10 characters total.</li>
  <li><strong>SDL References</strong> — Prefixed with <code class="language-plaintext highlighter-rouge">L</code>, 10 characters total.</li>
</ul>

<figure>
    <img src="https://www.penkin.me/assets/img/SAID.jpg" alt="Screenshot of the extension in action." />
    <figcaption>Fig2. - Screenshot of the extension in action</figcaption>
</figure>

<p>Every number it generates passes its respective validation algorithm. You can generate up to 25 at a time and copy them individually or all at once.</p>

<h2 id="the-validation-algorithms">The Validation Algorithms</h2>

<p>This is the part that makes generating these numbers non-trivial. You can’t just throw random digits together.</p>

<p><strong>SA ID numbers</strong> use the <a href="https://en.wikipedia.org/wiki/Luhn_algorithm">Luhn algorithm</a>. The first 6 digits are your date of birth, then 4 digits for gender (0000-4999 for female, 5000-9999 for male), a citizenship digit, an unused digit, and finally a check digit calculated from all the preceding digits.</p>

<p><strong>SARS tax numbers</strong> (income tax, PAYE, UIF, SDL) use a variation of Modulus 10. It’s similar to Luhn but processes digits left to right instead of right to left. For PAYE, UIF, and SDL numbers specifically, the algorithm substitutes the first character with <code class="language-plaintext highlighter-rouge">4</code> before computing the check digit.</p>

<figure>
    <img src="https://www.penkin.me/assets/img/PAYE.jpg" alt="Screenshot of the PAYE section." />
    <figcaption>Fig3. - Screenshot of the PAYE section</figcaption>
</figure>

<p>Here’s the SARS Modulus 10 implementation:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">calculateSarsModulus10CheckDigit</span><span class="p">(</span><span class="nx">digits</span><span class="p">,</span> <span class="nx">substituteFirst</span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">arr</span> <span class="o">=</span> <span class="nx">digits</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">''</span><span class="p">).</span><span class="nx">map</span><span class="p">(</span><span class="nb">Number</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">substituteFirst</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">arr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nx">substituteFirst</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="kd">let</span> <span class="nx">sum</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">val</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">val</span> <span class="o">*=</span> <span class="mi">2</span><span class="p">;</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">val</span> <span class="o">&gt;</span> <span class="mi">9</span><span class="p">)</span> <span class="nx">val</span> <span class="o">-=</span> <span class="mi">9</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nx">sum</span> <span class="o">+=</span> <span class="nx">val</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="p">(</span><span class="mi">10</span> <span class="o">-</span> <span class="p">(</span><span class="nx">sum</span> <span class="o">%</span> <span class="mi">10</span><span class="p">))</span> <span class="o">%</span> <span class="mi">10</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Not rocket science, but it’s the kind of thing you don’t want to be doing by hand every time you need a test number.</p>

<h2 id="how-its-built">How It’s Built</h2>

<p>The extension is intentionally simple. Vanilla JavaScript with ES6 modules, no build step, no frameworks. Each reference type has its own generator module, and shared validation logic lives in utility files.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── popup.html / popup.css / popup.js
├── js/
│   ├── generators/
│   │   ├── sa-id.js
│   │   ├── income-tax.js
│   │   ├── company-reg.js
│   │   ├── paye.js
│   │   ├── uif.js
│   │   └── sdl.js
│   └── utils/
│       ├── helpers.js
│       ├── luhn.js
│       └── sars-modulus10.js
</code></pre></div></div>

<p>It runs entirely locally. The only permission it needs is <code class="language-plaintext highlighter-rouge">clipboardWrite</code> for the copy functionality. No network requests, no data collection, no analytics.</p>

<p>I used Chrome’s Manifest V3, and there’s a GitHub Actions workflow that validates the tag version against <code class="language-plaintext highlighter-rouge">manifest.json</code> and produces a Chrome Web Store-ready zip on each release.</p>

<h2 id="give-it-a-try">Give It a Try</h2>

<p>If you’re working with South African tax systems, payroll, or company registration flows, this might save you some time. It’s free and open source.</p>

<ul>
  <li><a href="https://chromewebstore.google.com/detail/sars-testing-tools/lkgdflhcanokafddhpmmfnghpdhhgcka">Chrome Web Store</a></li>
  <li><a href="https://github.com/penkin/SARS-Testing-Tools-Browser-Extension">GitHub</a></li>
</ul>

<p>It’s a small tool, but its really useful.</p>]]></content><author><name>Christopher Penkin</name></author><category term="development" /><category term="tools" /><category term="testing" /><category term="chrome-extension" /><summary type="html"><![CDATA[A Chrome extension that generates valid South African tax reference numbers for testing. SA ID numbers, income tax, company registration, PAYE, UIF, and SDL references; all with proper check digit validation.]]></summary></entry><entry><title type="html">How I’m Actually Using Claude in My Daily Workflow</title><link href="https://www.penkin.me/ai/development/tools/productivity/2026/02/09/claude-workflow.html" rel="alternate" type="text/html" title="How I’m Actually Using Claude in My Daily Workflow" /><published>2026-02-09T00:00:00+00:00</published><updated>2026-02-09T00:00:00+00:00</updated><id>https://www.penkin.me/ai/development/tools/productivity/2026/02/09/claude-workflow</id><content type="html" xml:base="https://www.penkin.me/ai/development/tools/productivity/2026/02/09/claude-workflow.html"><![CDATA[<p>We did a quick knowledge-sharing session at work recently where I showed the team how I’ve been using <a href="https://claude.com/product/claude-code">Claude Code</a>. I thought it would be worth capturing some of those insights here for anyone else who may be interested.</p>

<h2 id="two-different-tools-for-two-different-jobs">Two Different Tools for Two Different Jobs</h2>

<p>I’ve settled into a pattern where I use two different Claude tools for different purposes. I use the <a href="https://claude.com/download">Claude Desktop App</a> for quick, back-and-forth questions. Like when I was figuring out <a href="https://flutter.dev/">Flutter</a> stuff for a potential project, or working through versioning for project’s build pipeline. It’s great for that kind of exploratory conversation where I’m just trying to understand something or bouncing thoughts and ideas around.</p>

<p>For actual project work where I’m making code changes, I use Claude Code.</p>

<p>I’m a terminal person; I’ve been on Linux/OSX for years, so I’m super comfortable in the command line. That makes Claude Code feel really natural to me.</p>

<h2 id="the-claudemd-file-is-your-secret-weapon">The CLAUDE.md File is Your Secret Weapon</h2>

<p>Here’s something that makes a huge difference: when you start a Claude Code project, you get this <code class="language-plaintext highlighter-rouge">CLAUDE.md</code> when you run the <code class="language-plaintext highlighter-rouge">/init</code> command. What it does is analyse your codebase, figure out how everything works together, what libraries you’re using, how your components are structured; basically builds this whole context about your project.</p>

<p>The cool thing is you can add rules to this file. Like if Claude keeps putting your controllers in the wrong directory, you just tell it in the <code class="language-plaintext highlighter-rouge">CLAUDE.md</code> file: “Controllers go here.” Or you can say “We’re doing TDD, so always write the test first, make it fail, then write the code.” You give it these guidelines and it follows them.</p>

<p>If you’re just getting started with Claude Code, I highly recommend watching <a href="https://www.youtube.com/watch?v=lJNjDoJi6hQ">this video on creating an effective CLAUDE.md file</a>; it’s a great primer on how to set up your project context properly.</p>

<div class="embed-container">
    <iframe src="https://www.youtube.com/embed/lJNjDoJi6hQ" width="700" height="480" frameborder="0" allowfullscreen="true">
    </iframe>
  </div>

<h2 id="plan-mode-changes-everything">Plan Mode Changes Everything</h2>

<p>The real game-changer for me has been plan mode. Let me give you a real example.</p>

<p>We had a ticket to add postal code fields to employee addresses. The team estimated it at four hours because there was front-end work, a detail view, and potentially API work if the backend didn’t support it.</p>

<p>I went into plan mode and basically said: “In the employee screen we have address line 1, line 2, city, postal code, province, and country. But in the company view we’re missing some of these fields.”</p>

<p>Claude came back with a plan. This is what I love about it; it doesn’t just start coding. It says “Okay, here’s how I’m thinking about this. I need to modify these files. Here’s what needs to change in the form. I see there’s a handle change function, I’ll need to update that. I checked your API and it already supports all these fields, so this is just a UI change.”</p>

<p>Then it lists out exactly what it’s going to verify to make sure it worked.</p>

<p>We were in standup. I hit enter on that prompt. Five minutes later, the whole thing was done. Data saving, everything looking exactly like the rest of the system. A four-hour task done in five minutes.</p>

<h2 id="its-like-working-with-a-junior-programmer">It’s Like Working with a Junior Programmer</h2>

<p>Here’s how I think about Claude: it’s a junior programmer.</p>

<p>If I just gave a junior dev a big spec file and said “make this happen,” I’d get the same unpredictable results I’d get from Claude. You need to guide it. You need to break things down. You need to explain things.</p>

<p>What Claude is really good at is going through all your files, understanding how everything works together, what talks to what. It has documentation for every library you’re using. It’s just really good at that stuff.</p>

<p>What’s important though is you need to understand context of the environment you’re working in. If it is your junior to work with, then you need to guide it, break things down, give it the right context etc.</p>

<h2 id="when-i-actually-use-it">When I Actually Use It</h2>

<p>Someone asked if I use it for complex business logic. Yup, I do.</p>

<p>For example, we had these identification fields that all needed specific validation. I had a screenshot of the requirements. I literally just copied the image, pasted it into Claude, and said “We need to add validation to these fields.”</p>

<p>It’s really good at understanding images and designs, especially if you already have a component system. It figures out how to make things look consistent.</p>

<p>But I broke it up. I told it to do the PAYE reference first. Then the SDL reference. Then the UIF reference. I didn’t just dump the whole thing on it.</p>

<h2 id="clear-the-context-between-tasks">Clear the Context Between Tasks</h2>

<p>One thing I do religiously after finishing a feature, I clear the context. LLMs work better with smaller contexts. You don’t want to give it this massive history of everything you’ve ever done in the project.</p>

<p>I keep tasks small and focused. Plan one thing, check the plan, do the thing, clear the context, rinse and repeat.</p>

<h2 id="the-planning-conversation-matters">The Planning Conversation Matters</h2>

<p>When you’re in plan mode, Claude will ask clarifying questions if it’s not sure about something. Like when I asked it to move the address fields to the right column, it asked: “Should the same change be applied to the read-only detail view?”</p>

<p>It was great because in text it kind of draws out the screen with the main labels on the page. You can see visually what it’s planning to do before it does it. You can catch mistakes early. You can say “Actually, no, put the address at the bottom, not the top.”</p>

<p>It’s like pair programming. You’re having a conversation about what needs to happen.</p>

<h2 id="i-still-review-everything">I Still Review Everything</h2>

<p>This is critical: you still need to review your code.</p>

<p>I still use my IDE to look at diffs. I go through each change and make sure it makes sense. I’m checking that it didn’t break anything. I’m verifying the logic is correct.</p>

<p>Claude is a tool, not a replacement for thinking. I’m the one who understands the problem, makes the decisions, and takes responsibility for the code. Claude just helps me get there faster.</p>

<h2 id="cross-project-work">Cross-Project Work</h2>

<p>Oh, and another thing—Claude can work across projects. On one of my projects, I’ve given it access to both the main UI project and the API/data projects. It knows about both. So when I’m making a change in it, it can go check the API to see if it already supports what I need.</p>

<p>You just tell it in the <code class="language-plaintext highlighter-rouge">CLAUDE.md</code> file where these other projects are, and it does the rest.</p>

<h2 id="just-start-using-it">Just Start Using It</h2>

<p>My advice? Just start using it. Even if you’re not having it write code for you, use it to ask questions about the codebase. Use it to understand problems and potential solutions. Ask it why it did something. Don’t just accept the code and move on. Use it as a learning tool. Build your understanding as you go.</p>

<p>It’s not about having the tool do everything for you. It’s about getting into the habit of using it to help you work more effectively. The four-hour tasks that take five minutes? Those add up. But only if you actually use the tool.</p>

<h2 id="theres-so-much-more-to-explore">There’s So Much More to Explore</h2>

<p>I should mention what I’ve shared here is really just scratching the surface. There’s a whole ecosystem around Claude Code that I’m still learning about.</p>

<p>Skills, for instance. You can create custom skills that teach Claude how to do specific things in specific ways for your projects. Think of them like specialised training modules.</p>

<p>Then there are <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a> servers that let Claude integrate with external tools and services. Want Claude to interact with your Slack workspace? Your Google Drive? Your database? MCP servers make that possible.</p>

<p>Some people are even creating orchestration setups where they use different Claude models for different tasks. Opus for planning and strategy, Sonnet for the actual coding, specialized agents for code review. They run tasks in parallel. It’s pretty wild what’s possible.</p>]]></content><author><name>Christopher Penkin</name></author><category term="ai" /><category term="development" /><category term="tools" /><category term="productivity" /><summary type="html"><![CDATA[A practical look at how I use Claude for real development work—from the Claude.md file setup to plan mode workflows, treating it like a junior programmer, and the importance of always reviewing your code.]]></summary></entry><entry><title type="html">Sometimes You’re Building People, and the Product is Just the By-Product</title><link href="https://www.penkin.me/remote/development/culture/people/2026/02/03/building-people.html" rel="alternate" type="text/html" title="Sometimes You’re Building People, and the Product is Just the By-Product" /><published>2026-02-03T00:00:00+00:00</published><updated>2026-02-03T00:00:00+00:00</updated><id>https://www.penkin.me/remote/development/culture/people/2026/02/03/building-people</id><content type="html" xml:base="https://www.penkin.me/remote/development/culture/people/2026/02/03/building-people.html"><![CDATA[<p>I used to be the guy who would roll his eyes at anyone suggesting office work had benefits. Remote work was <em>the</em> answer. The only answer. If it wasn’t working for a company, that company simply wasn’t trying hard enough. Full stop.</p>

<p>Well, I was wrong. Or at least, I was seeing it in black and white when there’s a whole lot of grey in between.</p>

<figure>
    <img src="https://www.penkin.me/assets/img/watercooler.jpg" alt="The proverbial watercooler." />
    <figcaption>Fig1. - The proverbial watercooler</figcaption>
</figure>

<p>About a month ago, I went back to the office at <a href="https://www.digitalsolutionfoundry.co.za/">Digital Solution Foundry</a>. Full time. Not hybrid, not a couple days a week - full time. And it’s completely challenged how I think about remote work.</p>

<h2 id="what-changed">What Changed?</h2>

<p>Here’s the thing; I still think remote work is great. But I’ve realized it’s not great for <em>everything</em>. And that’s a tough pill to swallow when you’ve been so vocally pro-remote-only for years.</p>

<p>At DSF, we have this core value around lifting people up. We take on interns, junior developers, people who might only have a year or less of experience (or even none). We’re not just building software; we’re teaching people how to build software. We’re helping them learn the craft, grow their skills, become better developers.</p>

<p>And that’s where remote work showed its limitations for us.</p>

<h3 id="the-goldmine-of-organic-conversations">The Goldmine of Organic Conversations</h3>

<p>And that’s where remote work showed its limitations for us.
In the last month, I’ve noticed something I completely missed when I was remote. There are these little conversations that happen organically in an office that you just don’t get over <a href="https://slack.com/">Slack</a> or <a href="https://www.microsoft.com/en-za/microsoft-teams/group-chat-software">Microsoft Teams</a>.</p>

<p>Research backs this up in a big way. A study from Harvard, the Federal Reserve Bank of New York, and the University of Iowa found that software engineers received 22% more feedback on their code when working in the same building as their teammates.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> Another study estimated that between 25-70% of work time is spent in face-to-face interactions, and roughly 30% of those are casual conversations; the kind that happen around the proverbial water cooler.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup></p>

<p>A junior developer is stuck on something. Instead of posting in a channel and waiting for a response, they turn to the person next to them. That person might be just as junior, and they work through it together; sometimes incorrectly, sometimes brilliantly. And I get to be there for that. I can jump in, not to take over, but to show them how a seasoned developer thinks about the problem. How to ask the right questions. How to get to the actual problem instead of plastering over a symptom. Oh and don’t forget the good old whiteboard and markers… unbeatable!</p>

<p>These aren’t scheduled mentoring sessions. They’re not formal pair programming exercises. They’re just moments. Quick five-minute conversations that happen because someone’s struggling and someone else is nearby. And those moments have been an absolute goldmine. Gallup research found that these informal workplace interactions can improve team performance by up to 50%.<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup></p>

<h2 id="the-nuance-i-missed">The Nuance I Missed</h2>

<p>Look, I’m not saying remote work is bad. I’m saying <em>it depends</em>.</p>

<p>For well-established teams with experienced developers who know what questions to ask and how to dig into problems? Remote works perfectly. Everyone has enough experience to be autonomous. Everyone knows how to communicate effectively asynchronously. The system works.</p>

<p>But when you’re building people up, when you’re teaching, when you’re creating an environment where inexperienced developers can learn from each other and from more experienced folks? That’s harder to replicate remotely. Studies have shown that remote and hybrid environments often fail to have effective structures to integrate, onboard, and train junior staff - which might explain why employee engagement has been declining in remote settings.<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup></p>

<p>And here’s something that really stood out to me: companies with formal mentoring programs see a 50% increase in employee engagement and retention.<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup> But the research also shows that less experienced workers struggle more with remote mentorship - they’re often unsure about the appropriate mode of communication, hesitant to reach out, and miss out on the kind of spontaneous learning that happens when you’re physically near more senior colleagues.<sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">6</a></sup></p>

<p>I used to think any company that couldn’t make remote work was just doing it wrong. And maybe some are. But I’ve realised that what you’re trying to achieve matters. <strong>Sometimes you’re building a product, and sometimes you’re building people and the by-product is the product.</strong></p>

<h2 id="what-i-believe-now">What I Believe Now</h2>

<p>Remote work is still the most flexible way to work. It offers freedom that’s hard to beat. But it’s not always the <em>best</em> way to work for what you’re trying to accomplish.</p>

<p>If you’re building with a team of senior developers working on a well-defined product? Remote might be perfect.</p>

<p>If you’re trying to create a learning environment where junior developers can grow through osmosis, through those little moments of connection and teaching? You might need people in the same physical space, at least some of the time.</p>

<p>The answer isn’t “remote is always better” or “office is always better.” The answer is “it depends on what you’re building and who you’re building it with.”</p>

<hr />

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Lane, J. N., &amp; others. (2024). “Effectively managing junior developers on remote teams.” <em>LeadDev</em>. <a href="https://leaddev.com/management/effectively-managing-junior-developers-remote-teams">https://leaddev.com/management/effectively-managing-junior-developers-remote-teams</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Whittaker, S., Frohlich, D., &amp; Daly-Jones, O. (1994). “Informal workplace communication: What is it like and how might we support it?” Referenced in: “Why Water Cooler Conversations Are Essential to Work Productivity.” <em>VSee Blog</em>. <a href="https://vsee.com/blog/watercooler-conversations-work-productivity/">https://vsee.com/blog/watercooler-conversations-work-productivity/</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>“50+ Engaging Water Cooler Questions &amp; Topics for Work.” <em>CultureBot</em>. <a href="https://getculturebot.com/blog/water-cooler-questions/">https://getculturebot.com/blog/water-cooler-questions/</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>Tsipursky, G. (2022). “Remote Mentoring for Effective Integration of Junior Employees.” <em>Disaster Avoidance Experts</em>. <a href="https://disasteravoidanceexperts.com/remote-mentoring-for-effective-integration-of-junior-employees/">https://disasteravoidanceexperts.com/remote-mentoring-for-effective-integration-of-junior-employees/</a> <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p>“7 Tips for Mentoring Junior Developers.” <em>Revelo</em>. <a href="https://www.revelo.com/blog/how-to-mentor-junior-developers">https://www.revelo.com/blog/how-to-mentor-junior-developers</a> <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6" role="doc-endnote">
      <p>“New Report Highlights Best Practices for Mentoring Junior Employees in a Remote or Hybrid Environment.” <em>The Chronicle of Evidence-Based Mentoring</em>. <a href="https://www.evidencebasedmentoring.org/redefining-mentorship-for-the-hybrid-generation/">https://www.evidencebasedmentoring.org/redefining-mentorship-for-the-hybrid-generation/</a> <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Christopher Penkin</name></author><category term="remote" /><category term="development" /><category term="culture" /><category term="people" /><summary type="html"><![CDATA[From remote-only advocate to nuanced believer. How mentoring junior developers in-person revealed the limitations of remote work I'd been missing.]]></summary></entry><entry><title type="html">A Second Look at Dia: Pleasantly Surprised</title><link href="https://www.penkin.me/browsers/arc/dia/2026/01/09/arc-to-dia-revisited.html" rel="alternate" type="text/html" title="A Second Look at Dia: Pleasantly Surprised" /><published>2026-01-09T00:00:00+00:00</published><updated>2026-01-09T00:00:00+00:00</updated><id>https://www.penkin.me/browsers/arc/dia/2026/01/09/arc-to-dia-revisited</id><content type="html" xml:base="https://www.penkin.me/browsers/arc/dia/2026/01/09/arc-to-dia-revisited.html"><![CDATA[<p>Not too long ago I wrote about <a href="https://www.penkin.me/browsers/arc/dia/zen/2025/07/10/arc-to-dia.html">why I couldn’t move from Arc to Dia</a>. I was pretty harsh on Dia, and honestly, I thought that was going to be the end of my relationship with it. My Applications folder was going to be its final resting place.</p>

<p>Well, I was wrong.</p>

<p>I decided to give Dia another shot after seeing their <a href="https://www.diabrowser.com/release-notes/latest">release notes on tab groups</a>, and I have to say, I’m pleasantly surprised. Actually, I’m more than pleasantly surprised; I’m genuinely optimistic about where this browser is headed.</p>

<h2 id="what-changed">What Changed?</h2>

<p>Let me address the exact concerns I had in my original post, because Dia has either fixed them or I’ve come to appreciate their approach differently this time around.</p>

<h3 id="spaces-profiles-are-actually-fine">Spaces (Profiles) Are Actually… Fine?</h3>

<p>Look, I’m not going to pretend that Dia’s profiles work exactly like Arc’s spaces. They don’t. But here’s the thing; I was so focused on how they were <em>different</em> that I didn’t appreciate how they could work <em>for</em> me.</p>

<p>Yes, they still open in separate windows. Yes, the navigation isn’t quite as slick as Arc’s mouse gesture switching. But after using them for a bit, I’ve found they actually fit my workflow really well. The separation is more pronounced, which means when I’m in my work profile, I’m <em>in</em> my work profile. No accidental mixing of personal and work stuff. It’s grown on me.</p>

<h3 id="the-sidebar-is-here">The Sidebar Is Here!</h3>

<p>This was a big one for me. When I first tried Dia, having tabs only at the top felt like a step backwards. I know they did add this feature in a while back but it was still not great. They still had no folders but that’s changed. What is great though is seeing how they have been listening and adding features in like the sidebar, thoughtfully. I can toggle between top and side tabs based on what I’m doing, but I find myself using the sidebar most of the time. It just feels right having that vertical real estate back on a large screen.</p>

<h3 id="tab-groups-almost-better-than-folders">Tab Groups: Almost Better Than Folders</h3>

<p>Here’s where Dia really surprised me. Their new <a href="https://www.diabrowser.com/changelog">tab groups feature</a> (released in December) is… really good? Like, really, really good.</p>

<figure>
    <img src="https://www.penkin.me/assets/img/dia-groups.png" alt="Dia browser showing its tab groups." />
    <figcaption>Fig1. - Dia Tab Groups</figcaption>
</figure>

<p>They’re colour-coded, which makes scanning through my work so much easier. I can organize by project, by client, by whatever makes sense at the time. They’re customisable and they stay put, so when I come back to my work, everything is exactly where I left it. The tab groups with the colour coding feels good and quick to recognise where I am and where I want to be.</p>

<p>There are still no live folders for things like GitHub Pull Requests but that is no deal breaker. Would be really great if they did add live folders back in but in a smart way that work with more providers or in some kind of plugin way that the community could contribute to. That would be pretty awesome.</p>

<h3 id="splits-are-here-too">Splits Are Here Too</h3>

<figure>
    <img src="https://www.penkin.me/assets/img/dia-splits.png" alt="Dia browser showing tab splits." />
    <figcaption>Fig2. - Dia Tab Splits</figcaption>
</figure>

<p>Tab splits. They’re here. I can put my Figma designs next to my working code. I can compare documents side by side. It works exactly like I need it to work. Check that box.</p>

<h2 id="what-im-excited-about">What I’m Excited About</h2>

<p>The team is clearly listening to feedback. They’re not just building an Arc clone; they’re building something that learns from Arc’s strengths while going in their own direction. Looking at their release notes, they’re shipping meaningful updates.</p>

<p>The fact that they’ve added features like picture-in-picture for Google Meet, focus mode, and are constantly refining their AI capabilities shows they’re playing the long game. They’re not just trying to win back Arc users with feature parity; they’re building toward something bigger.</p>

<p>And that AI integration? I haven’t fully explored it yet, but I’m keen to see how valuable it becomes. The Skills feature, the ability to interact with tabs contextually, the Memory system. They’re genuinely trying to reimagine what a browser can do for you. If they can make these AI features truly useful rather than just flashy, Dia could end up being something really special.</p>

<p>The future of Dia looks genuinely bright. The Browser Company learned a lot with Arc, and now they’re applying those lessons with clear intention and impressive execution. I’m excited to be along for the ride.</p>

<h2 id="the-verdict">The Verdict</h2>

<p>I’m moving to Dia. Not begrudgingly. Not with reservations. I’m genuinely excited to use it.</p>

<p><a href="https://arc.net/">Arc</a> was incredible, and it’ll always be the browser that showed me what a browser could be. But Dia has answered my concerns, addressed my pain points, and shown me they’re committed to making a browser that works for real workflows.</p>

<p>See you on the other side.</p>]]></content><author><name>Christopher Penkin</name></author><category term="browsers" /><category term="arc" /><category term="dia" /><summary type="html"><![CDATA[After criticising Dia browser for lacking Arc's best features, I gave it another chance. With tab groups, sidebar support, and splits now available, Dia has addressed my concerns and earned my switch from Arc.]]></summary></entry><entry><title type="html">Building an Omarchy-Inspired Setup on macOS</title><link href="https://www.penkin.me/development/tools/productivity/configuration/2025/11/28/building-omarchy-inspired-setup-macos.html" rel="alternate" type="text/html" title="Building an Omarchy-Inspired Setup on macOS" /><published>2025-11-28T00:00:00+00:00</published><updated>2025-11-28T00:00:00+00:00</updated><id>https://www.penkin.me/development/tools/productivity/configuration/2025/11/28/building-omarchy-inspired-setup-macos</id><content type="html" xml:base="https://www.penkin.me/development/tools/productivity/configuration/2025/11/28/building-omarchy-inspired-setup-macos.html"><![CDATA[<p>When I first discovered <a href="https://omarchy.org/">Omarchy</a>, I loved how clean and out or the way it felt. I loved it’s 
keyboard-driven approach to desktop computing. The tiling window management via Hyprland, the minimal aesthetic with 
sharp corners, and the snappy, animation-free workspace switching spoke to my sensibilities. There was just one 
problem: my MacBook Pro M1 can’t run Omarchy as a Linux distribution.</p>

<figure>
    <img src="https://www.penkin.me/assets/img/tiling-screenshot.png" alt="Example of how it looks." />
    <figcaption>Fig1. - My coding setup</figcaption>
</figure>

<p>Rather than compromise, I decided to recreate the Omarchy experience on macOS (sort of). This post documents my journey building 
a sophisticated tiling window manager setup using <code class="language-plaintext highlighter-rouge">yabai</code>, <code class="language-plaintext highlighter-rouge">sketchybar</code>, and <code class="language-plaintext highlighter-rouge">skhd</code> - bringing the Omarchy philosophy 
to the Mac.</p>

<h2 id="the-vision">The Vision</h2>

<p>What makes Omarchy special isn’t just the tiling - it’s the philosophy behind it:</p>

<ul>
  <li><strong>Keyboard-first interaction</strong>: Mouse usage should be minimal</li>
  <li><strong>Instant workspace switching</strong>: No animations, no delays</li>
  <li><strong>Clean aesthetics</strong>: Sharp corners, minimal chrome, purposeful design</li>
  <li><strong>Focus-driven workflow</strong>: One task, one workspace, maximum concentration</li>
</ul>

<p>While I can’t run Hyprland on macOS, I can absolutely adopt these principles using the right tools.</p>

<h2 id="the-foundation-yabai--sketchybar--skhd">The Foundation: yabai + sketchybar + skhd</h2>

<p>My setup relies on three complementary tools:</p>

<ul>
  <li><strong><a href="https://github.com/koekeishiya/yabai">yabai</a></strong>: A tiling window manager that gives us BSP (Binary Space Partitioning) layouts and workspace management</li>
  <li><strong><a href="https://github.com/FelixKratz/SketchyBar">sketchybar</a></strong>: A customizable status bar that replaces the macOS menu bar</li>
  <li><strong><a href="https://github.com/koekeishiya/skhd">skhd</a></strong>: A hotkey daemon for binding keyboard shortcuts</li>
</ul>

<p>Together, these tools transform macOS into a keyboard-driven, tiling-focused environment that feels remarkably close to Omarchy.</p>

<h2 id="the-scripting-addition-challenge">The Scripting Addition Challenge</h2>

<p>Getting full workspace control with yabai required installing its “scripting addition,” which meant partially disabling 
System Integrity Protection (SIP). This was one of the trickier aspects of the setup - I had to reboot into Recovery Mode 
and selectively disable certain SIP protections while keeping others enabled for security.</p>

<p>After modifying SIP, I installed yabai’s scripting addition and configured passwordless sudo for it in <code class="language-plaintext highlighter-rouge">/private/etc/sudoers.d/yabai</code> 
so it could load automatically on startup. I added <code class="language-plaintext highlighter-rouge">sudo yabai --load-sa</code> at the top of my <code class="language-plaintext highlighter-rouge">~/.config/yabai/yabairc</code> to 
ensure it loads every time yabai starts.</p>

<h2 id="yabai-configuration-tiling-that-works">yabai Configuration: Tiling That Works</h2>

<p>My yabai configuration is focused on creating a clean, predictable tiling environment:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># BSP layout for automatic tiling</span>
yabai <span class="nt">-m</span> config layout bsp

<span class="c"># Padding and gaps for visual breathing room</span>
yabai <span class="nt">-m</span> config top_padding    42  <span class="c"># Accounts for sketchybar height</span>
yabai <span class="nt">-m</span> config bottom_padding 8
yabai <span class="nt">-m</span> config left_padding   8
yabai <span class="nt">-m</span> config right_padding  8
yabai <span class="nt">-m</span> config window_gap     8

<span class="c"># Mouse follows focus for smoother workflow</span>
yabai <span class="nt">-m</span> config mouse_follows_focus on

<span class="c"># Keep certain apps floating</span>
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s2">"^System Settings$"</span> <span class="nv">manage</span><span class="o">=</span>off
</code></pre></div></div>

<p>The padding configuration is crucial - the 42-pixel top padding accounts for my 34-pixel sketchybar plus some margin, 
preventing windows from sliding over my sketchybar.</p>

<h2 id="opening-apps-on-certain-workspaces">Opening apps on certain workspaces</h2>

<p>One of the pros of this setup is getting into a flow with how you work. One of the other things that I did was to open
certain apps on certain workspaces. This adds to the predictability of always knowing which of my common apps are where
so when I need to message someone on slack I know its on workspace 3 always etc.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s2">"^Arc$"</span> <span class="nv">space</span><span class="o">=</span>1
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s2">"^Ghostty$"</span> <span class="nv">space</span><span class="o">=</span>2
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s2">"^Microsoft Teams$"</span> <span class="nv">space</span><span class="o">=</span>3
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s2">"^Slack$"</span> <span class="nv">space</span><span class="o">=</span>3
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s2">"^WhatsApp$"</span> <span class="nv">space</span><span class="o">=</span>4
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s2">"^Signal$"</span> <span class="nv">space</span><span class="o">=</span>4
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s2">"^Discord$"</span> <span class="nv">space</span><span class="o">=</span>4
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s2">"^Proton Mail$"</span> <span class="nv">space</span><span class="o">=</span>5
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s2">"^Mail$"</span> <span class="nv">space</span><span class="o">=</span>6
yabai <span class="nt">-m</span> rule <span class="nt">--add</span> <span class="nv">app</span><span class="o">=</span><span class="s2">"^Calendar$"</span> <span class="nv">space</span><span class="o">=</span>6
</code></pre></div></div>

<p>I did however run into some really strange behaviour doing this. Often for some reason suddently all my workspace 2 apps were
on workspace 1 or 3 were on 4! That is when I found out that MacOS does some fancy workspace fiddling depening on how you use
them.</p>

<p>To disable this you can easily do it in System Settings, go to <code class="language-plaintext highlighter-rouge">Desktop &amp; Dock</code> and unchecking 
<code class="language-plaintext highlighter-rouge">Automatically rearrange Spaces based on most recent use</code>.</p>

<figure>
    <img src="https://www.penkin.me/assets/img/system-settings-dock.png" alt="System Settings for Desktop &amp; Dock." />
    <figcaption>Fig2. - System Settings</figcaption>
</figure>

<h3 id="multi-monitor-support">Multi-Monitor Support</h3>

<p>One of the key improvements I made was proper multi-monitor support. When I connect my external display, I want spaces 
1-6 on my main monitor and spaces 7-12 on the secondary display. This required careful configuration of both yabai and 
sketchybar to understand display relationships.</p>

<h2 id="sketchybar-the-minimal-status-bar">sketchybar: The Minimal Status Bar</h2>

<p>My sketchybar configuration embraces minimalism while providing essential information:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Bar appearance - dark with sharp corners</span>
sketchybar <span class="nt">--bar</span> <span class="nv">position</span><span class="o">=</span>top <span class="se">\</span>
                 <span class="nv">margin</span><span class="o">=</span>0 <span class="se">\</span>
                 <span class="nv">y_offset</span><span class="o">=</span>0 <span class="se">\</span>
                 <span class="nv">corner_radius</span><span class="o">=</span>0 <span class="se">\</span>
                 <span class="nv">height</span><span class="o">=</span>34 <span class="se">\</span>
                 <span class="nv">color</span><span class="o">=</span>0xC0000000

<span class="c"># Defaults for all items - light grey text on dark background</span>
<span class="nv">default</span><span class="o">=(</span>
  <span class="nv">padding_left</span><span class="o">=</span>0
  <span class="nv">padding_right</span><span class="o">=</span>4
  icon.font<span class="o">=</span><span class="s2">"JetBrainsMono Nerd Font:Regular:14.0"</span>
  label.font<span class="o">=</span><span class="s2">"JetBrainsMono Nerd Font:Regular:14.0"</span>
  icon.color<span class="o">=</span>0xff9e9e9e
  label.color<span class="o">=</span>0xff9e9e9e
  icon.highlight_color<span class="o">=</span>0xffffffff
  label.highlight_color<span class="o">=</span>0xffffffff
<span class="o">)</span>
</code></pre></div></div>

<h3 id="workspace-indicators">Workspace Indicators</h3>

<p>The workspace indicators dynamically update to show which spaces belong to which display:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Add space items</span>
<span class="k">for </span>i <span class="k">in</span> <span class="o">{</span>1..10<span class="o">}</span><span class="p">;</span> <span class="k">do
  </span>sketchybar <span class="nt">--add</span> space space.<span class="nv">$i</span> left <span class="se">\</span>
             <span class="nt">--set</span> space.<span class="nv">$i</span> <span class="se">\</span>
                   <span class="nv">associated_space</span><span class="o">=</span><span class="nv">$i</span> <span class="se">\</span>
                   <span class="nv">icon</span><span class="o">=</span><span class="nv">$i</span> <span class="se">\</span>
                   <span class="nv">script</span><span class="o">=</span><span class="s2">"</span><span class="nv">$PLUGIN_DIR</span><span class="s2">/spaces.sh"</span>
<span class="k">done</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">spaces.sh</code> plugin queries yabai to determine the current space and display, highlighting the active workspace.</p>

<h3 id="status-items">Status Items</h3>

<p>On the right side, I keep things minimal but informative:</p>

<ul>
  <li><strong>WiFi</strong>: Shows connection status</li>
  <li><strong>Battery</strong>: Current charge level</li>
  <li><strong>Volume</strong>: Audio level indicator</li>
  <li><strong>Clock</strong>: Time display</li>
</ul>

<p>Each uses a simple plugin script that updates on relevant events.</p>

<h2 id="skhd-keyboard-driven-everything">skhd: Keyboard-Driven Everything</h2>

<p>The real magic happens in my skhd configuration. I’ve mapped every window management operation to keyboard shortcuts, 
drawing inspiration from both Omarchy and vim:</p>

<h3 id="window-management">Window Management</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Terminal</span>
cmd - <span class="k">return</span> : ghostty +new-window

<span class="c"># Window operations</span>
cmd - w : yabai <span class="nt">-m</span> window <span class="nt">--close</span>
cmd - t : yabai <span class="nt">-m</span> window <span class="nt">--toggle</span> float
cmd - f : yabai <span class="nt">-m</span> window <span class="nt">--toggle</span> zoom-fullscreen

<span class="c"># Focus windows (vim keys)</span>
cmd - h : yabai <span class="nt">-m</span> window <span class="nt">--focus</span> west
cmd - j : yabai <span class="nt">-m</span> window <span class="nt">--focus</span> south
cmd - k : yabai <span class="nt">-m</span> window <span class="nt">--focus</span> north
cmd - l : yabai <span class="nt">-m</span> window <span class="nt">--focus</span> east

<span class="c"># Move/Swap windows</span>
cmd + <span class="nb">shift</span> - h : yabai <span class="nt">-m</span> window <span class="nt">--swap</span> west
cmd + <span class="nb">shift</span> - j : yabai <span class="nt">-m</span> window <span class="nt">--swap</span> south
cmd + <span class="nb">shift</span> - k : yabai <span class="nt">-m</span> window <span class="nt">--swap</span> north
cmd + <span class="nb">shift</span> - l : yabai <span class="nt">-m</span> window <span class="nt">--swap</span> east

<span class="c"># Resize windows</span>
cmd + alt - h : yabai <span class="nt">-m</span> window <span class="nt">--resize</span> left:-30:0
cmd + alt - j : yabai <span class="nt">-m</span> window <span class="nt">--resize</span> bottom:0:30
cmd + alt - k : yabai <span class="nt">-m</span> window <span class="nt">--resize</span> top:0:-30
cmd + alt - l : yabai <span class="nt">-m</span> window <span class="nt">--resize</span> right:30:0
</code></pre></div></div>

<h3 id="workspace-switching">Workspace Switching</h3>

<p>The workspace switching is designed to work seamlessly across multiple displays:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Switch to workspace (accounts for multi-monitor)</span>
cmd - 1 : yabai <span class="nt">-m</span> space <span class="nt">--focus</span> <span class="si">$(</span>yabai <span class="nt">-m</span> query <span class="nt">--displays</span> <span class="nt">--display</span> | jq <span class="s1">'.spaces[0]'</span><span class="si">)</span>
cmd - 2 : yabai <span class="nt">-m</span> space <span class="nt">--focus</span> <span class="si">$(</span>yabai <span class="nt">-m</span> query <span class="nt">--displays</span> <span class="nt">--display</span> | jq <span class="s1">'.spaces[1]'</span><span class="si">)</span>
<span class="c"># ... through cmd - 0</span>

<span class="c"># Move window to workspace</span>
cmd + <span class="nb">shift</span> - 1 : yabai <span class="nt">-m</span> window <span class="nt">--space</span> <span class="si">$(</span>yabai <span class="nt">-m</span> query <span class="nt">--displays</span> <span class="nt">--display</span> | jq <span class="s1">'.spaces[0]'</span><span class="si">)</span>
cmd + <span class="nb">shift</span> - 2 : yabai <span class="nt">-m</span> window <span class="nt">--space</span> <span class="si">$(</span>yabai <span class="nt">-m</span> query <span class="nt">--displays</span> <span class="nt">--display</span> | jq <span class="s1">'.spaces[1]'</span><span class="si">)</span>
<span class="c"># ... through cmd + shift - 0</span>

<span class="c"># Quick workspace navigation</span>
cmd - tab : yabai <span class="nt">-m</span> space <span class="nt">--focus</span> next <span class="o">||</span> yabai <span class="nt">-m</span> space <span class="nt">--focus</span> first
cmd + <span class="nb">shift</span> - tab : yabai <span class="nt">-m</span> space <span class="nt">--focus</span> prev <span class="o">||</span> yabai <span class="nt">-m</span> space <span class="nt">--focus</span> last
</code></pre></div></div>

<h3 id="monitor-management">Monitor Management</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Switch between monitors</span>
cmd + ctrl - left : yabai <span class="nt">-m</span> display <span class="nt">--focus</span> west
cmd + ctrl - right : yabai <span class="nt">-m</span> display <span class="nt">--focus</span> east

<span class="c"># Move window to another monitor</span>
cmd + <span class="nb">shift</span> + ctrl - left : yabai <span class="nt">-m</span> window <span class="nt">--display</span> west<span class="p">;</span> yabai <span class="nt">-m</span> display <span class="nt">--focus</span> west
I did however run into some really strange behaviour doing this. Often <span class="k">for </span>some reason suddently all my workspace 2 apps were
</code></pre></div></div>

<h3 id="layout-management">Layout Management</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Layout operations</span>
cmd - e : yabai <span class="nt">-m</span> window <span class="nt">--toggle</span> <span class="nb">split
</span>cmd - b : yabai <span class="nt">-m</span> space <span class="nt">--balance</span>
cmd - r : yabai <span class="nt">-m</span> space <span class="nt">--rotate</span> 270
</code></pre></div></div>

<h3 id="screenshots">Screenshots</h3>

<p>I wanted screenshot functionality since often I need to take screenshots to attach to my PRs. What I did was
add configs to allow me to either take a full screenshot, an interactive screenshot (both to files) and then 
one that goes straight to clipboard:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Screenshots using 's' key</span>
cmd + <span class="nb">shift</span> - s : screencapture <span class="nt">-x</span> ~/Desktop/screenshot-<span class="si">$(</span><span class="nb">date</span> +%Y%m%d-%H%M%S<span class="si">)</span>.png
cmd + alt - s : screencapture <span class="nt">-i</span> <span class="nt">-x</span> ~/Desktop/screenshot-<span class="si">$(</span><span class="nb">date</span> +%Y%m%d-%H%M%S<span class="si">)</span>.png
cmd + ctrl - s : screencapture <span class="nt">-i</span> <span class="nt">-c</span> <span class="nt">-x</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">-x</code> ensures that you don’t hear that camera noise that screenshots normally makes. Nice and quiet.</p>

<h2 id="the-workflow">The Workflow</h2>

<p>With this setup, my typical workflow looks like this:</p>

<ol>
  <li><strong>Launch terminal</strong>: <code class="language-plaintext highlighter-rouge">cmd + return</code> opens a new Ghostty window</li>
  <li><strong>Switch workspaces</strong>: <code class="language-plaintext highlighter-rouge">cmd + 1</code> through <code class="language-plaintext highlighter-rouge">cmd + 0</code> for instant workspace switching</li>
  <li><strong>Focus windows</strong>: <code class="language-plaintext highlighter-rouge">cmd + h/j/k/l</code> for vim-style navigation</li>
  <li><strong>Move windows</strong>: <code class="language-plaintext highlighter-rouge">cmd + shift + h/j/k/l</code> to swap window positions</li>
  <li><strong>Multi-monitor</strong>: <code class="language-plaintext highlighter-rouge">cmd + ctrl + left/right</code> to switch between displays</li>
</ol>

<p>Everything is keyboard-driven, instant, and predictable. No animations, no delays - just pure efficiency.</p>

<h2 id="dotfiles-integration">Dotfiles Integration</h2>

<p>One of the benefits of using GNU Stow is how seamlessly this integrates with my overall dotfiles setup. My yabai, sketchybar, and 
skhd configurations live in their standard locations:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">~/.config/yabai/yabairc</code> - yabai window manager config</li>
  <li><code class="language-plaintext highlighter-rouge">~/.config/sketchybar/sketchybarrc</code> - status bar config</li>
  <li><code class="language-plaintext highlighter-rouge">~/.config/sketchybar/plugins/</code> - status bar plugins (spaces.sh, battery.sh, wifi.sh, volume.sh, clock.sh)</li>
  <li><code class="language-plaintext highlighter-rouge">~/.skhdrc</code> - keyboard shortcut daemon config</li>
</ul>

<p>These are organized in my dotfiles repository and managed with GNU Stow, making it easy to version control and deploy across machines. 
To set up these configurations on a new machine:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/dotfiles
./install-macos.sh  <span class="c"># Installs all dependencies via Homebrew</span>
<span class="c"># Then stow the relevant packages</span>
</code></pre></div></div>

<p>This modular approach means I can selectively install just the tools I need on each machine, whether it’s my full desktop setup or a 
minimal configuration on a remote server.</p>

<h2 id="lessons-learned">Lessons Learned</h2>

<h3 id="what-works-well">What Works Well</h3>

<ul>
  <li><strong>Keyboard-driven workflow</strong>: Once you internalize the keybindings, you rarely touch the mouse</li>
  <li><strong>Workspace-per-task</strong>: Having dedicated workspaces for different contexts (coding, browsing, communication) reduces mental overhead</li>
  <li><strong>Multi-monitor support</strong>: The dynamic workspace assignment across displays works beautifully</li>
  <li><strong>Minimal aesthetics</strong>: The clean status bar and sharp window edges create a focused environment</li>
</ul>

<h3 id="challenges">Challenges</h3>

<ul>
  <li><strong>SIP requirements</strong>: Partially disabling System Integrity Protection makes some users uncomfortable, though I haven’t experienced any issues</li>
  <li><strong>Learning curve</strong>: There’s definitely an adjustment period when moving from a traditional windowing system</li>
  <li><strong>Application compatibility</strong>: Some apps don’t play nicely with tiling (looking at you, System Settings)</li>
</ul>

<h3 id="compared-to-omarchy">Compared to Omarchy</h3>

<p>While this setup can’t fully replicate Omarchy’s Hyprland-powered experience, it captures the essential philosophy:</p>

<ul>
  <li>Keyboard-first interaction? ✓</li>
  <li>Instant workspace switching? ✓</li>
  <li>Clean, minimal aesthetic? ✓</li>
  <li>BSP tiling? ✓ (though not identical to Hyprland)</li>
</ul>

<p>The main differences are in polish and integration - Omarchy is a cohesive Linux distribution built around these concepts, while this is a 
collection of tools adapted to macOS. But for daily development work on a Mac, it gets remarkably close.</p>

<h2 id="try-it-yourself">Try It Yourself</h2>

<p>If you’re interested in seeing how I’ve configured everything, check out my <a href="https://github.com/penkin/dotfiles">dotfiles repository</a>. All 
my yabai, sketchybar, and skhd configurations are there, managed with GNU Stow for easy deployment.</p>

<p>Keep in mind this setup requires partially disabling SIP and has a learning curve as you internalize the keybindings. It’s also opinionated 
to my workflow - you’ll want to adapt it to your own preferences.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Building an Omarchy-inspired setup on macOS has transformed how I interact with my computer. The keyboard-driven workflow, combined with 
predictable tiling and instant workspace switching, has made me more focused and productive.</p>

<p>While it took some effort to configure and required compromises compared to running Omarchy natively on Linux, the result is a professional 
development environment that honors the Omarchy philosophy while working within macOS’s constraints.</p>

<p>If you value keyboard efficiency, minimal distractions, and a focus-driven workflow, I encourage you to explore tiling window management on 
macOS. The tools are mature, the community is active, and the productivity gains are real.</p>]]></content><author><name>Christopher Penkin</name></author><category term="Development" /><category term="Tools" /><category term="Productivity" /><category term="Configuration" /><summary type="html"><![CDATA[Learn how to build a keyboard-driven tiling window manager setup on macOS using yabai, sketchybar, and skhd. Inspired by Omarchy's philosophy of minimal aesthetics and instant workspace switching, this guide covers configuration, multi-monitor support, and creating a distraction-free development environment on a MacBook Pro M1.]]></summary></entry><entry><title type="html">My Dotfiles Setup with GNU Stow</title><link href="https://www.penkin.me/development/tools/productivity/configuration/2025/10/20/my-dotfiles-setup-with-gnu-stow.html" rel="alternate" type="text/html" title="My Dotfiles Setup with GNU Stow" /><published>2025-10-20T00:00:00+00:00</published><updated>2025-10-20T00:00:00+00:00</updated><id>https://www.penkin.me/development/tools/productivity/configuration/2025/10/20/my-dotfiles-setup-with-gnu-stow</id><content type="html" xml:base="https://www.penkin.me/development/tools/productivity/configuration/2025/10/20/my-dotfiles-setup-with-gnu-stow.html"><![CDATA[<h1 id="my-dotfiles-setup-with-gnu-stow">My Dotfiles Setup with GNU Stow</h1>

<p>I’ve been maintaining my <a href="https://www.freecodecamp.org/news/dotfiles-what-is-a-dot-file-and-how-to-create-it-in-mac-and-linux/">dotfiles</a> for a while now, and I’ve finally settled on a setup that works really well across both my macOS machine and my <a href="https://archlinux.org/">Arch Linux</a> box running <a href="https://omarchy.org/">Omarchy</a>. The key to making this work? <a href="https://www.gnu.org/software/stow/">GNU Stow</a>.</p>

<p>If you’re not familiar with GNU Stow, it’s a symlink farm manager. In plain English, it creates symbolic links from a central dotfiles directory to wherever your configs actually need to live. This means you can keep everything in one place, version control it all, and selectively install just the configs you need on any given machine.</p>

<h2 id="why-gnu-stow">Why GNU Stow?</h2>

<p>I haven’t really tried any of the other solutions out there but Stow just clicks for me because it’s:</p>

<ul>
  <li><strong>Simple</strong> - It’s just symlinks. Nothing fancy.</li>
  <li><strong>Modular</strong> - Each tool gets its own package that can be installed independently.</li>
  <li><strong>Transparent</strong> - You can see exactly what’s linked where.</li>
  <li><strong>Reversible</strong> - <code class="language-plaintext highlighter-rouge">stow -D</code> removes all the symlinks, no mess left behind.</li>
</ul>

<h2 id="the-structure">The Structure</h2>

<p>My dotfiles are organised into modular packages in <code class="language-plaintext highlighter-rouge">~/dotfiles</code>, with each tool or category getting its own directory:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~/dotfiles/
├── zsh/           # Shell configuration
├── git/           # Git configuration
├── nvim/          # Neovim editor
├── zellij/        # Terminal multiplexer
├── hyprland/      # Wayland compositor (Linux)
├── macos-tools/   # macOS-specific tools
└── ...
</code></pre></div></div>

<p>Each package contains the files in the structure they’d normally live in your home directory. For example, the <code class="language-plaintext highlighter-rouge">zsh</code> package has <code class="language-plaintext highlighter-rouge">.zshrc</code>, <code class="language-plaintext highlighter-rouge">.zsh-darwin.sh</code>, <code class="language-plaintext highlighter-rouge">.zsh-linux.sh</code>, and so on.</p>

<h2 id="the-setup-scripts">The Setup Scripts</h2>

<p>To make getting started easier, I created installation scripts for each platform. They handle package installation, prompt for optional components, and automatically stow the configs.</p>

<p>For Arch Linux:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/dotfiles
./install-arch.sh
</code></pre></div></div>

<p>For macOS:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/dotfiles
./install-macos.sh
</code></pre></div></div>

<p>These scripts will install the necessary packages (like zsh, neovim, zellij, fzf, etc.) and then selectively stow the configs you want.</p>

<h2 id="os-specific-configurations">OS-Specific Configurations</h2>

<p>One challenge with cross-platform dotfiles is handling the differences between operating systems. I solved this with OS-specific configuration files that get sourced automatically.</p>

<p>In my zsh setup, I have:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">.zsh-darwin.sh</code> - macOS-specific settings (Homebrew paths, ASDF, etc.)</li>
  <li><code class="language-plaintext highlighter-rouge">.zsh-linux.sh</code> - Linux-specific settings (Paru AUR helper, ASDF, etc.)</li>
</ul>

<p>The main <code class="language-plaintext highlighter-rouge">.zshrc</code> detects which OS you’re on and sources the appropriate file. This keeps the platform-specific cruft out of the main config.</p>

<h2 id="key-tools">Key Tools</h2>

<p>Here are just some of the tools I’m using currently across both systems:</p>

<h3 id="shell--terminal">Shell &amp; Terminal</h3>

<ul>
  <li><strong><a href="https://www.zsh.org/">zsh</a></strong> with Zinit plugin manager and Powerlevel10k prompt</li>
  <li><strong><a href="https://zellij.dev/">zellij</a></strong> as a modern terminal multiplexer</li>
  <li><strong><a href="https://junegunn.github.io/fzf/">fzf</a></strong> for fuzzy finding</li>
  <li><strong><a href="https://eza.rocks/">eza</a></strong> as a modern ls replacement</li>
  <li><strong><a href="https://crates.io/crates/zoxide">zoxide</a></strong> for smart directory jumping</li>
</ul>

<h3 id="development">Development:**</h3>
<ul>
  <li><strong><a href="https://zed.dev/">Zed</a></strong> editor (my new favourite)</li>
  <li><strong><a href="https://neovim.io/">neovim</a></strong> for editing</li>
  <li><strong><a href="https://github.com/jesseduffield/lazygit">lazygit</a></strong> for a nice Git TUI</li>
  <li><strong><a href="https://github.com/jesseduffield/lazydocker">lazydocker</a></strong> for a nice Docker TUI</li>
  <li><strong><a href="https://github.com/jorgerojas26/lazysql">lazysql</a></strong> for a nice SQL TUI</li>
</ul>

<h3 id="nic">Nic</h3>
<ul>
  <li><strong><a href="https://yazi-rs.github.io/">yazi</a></strong> as a terminal file manager</li>
  <li><strong><a href="https://github.com/aristocratos/btop">btop</a></strong> for system monitoring</li>
</ul>

<h3 id="archomarchy-desktop">Arch/Omarchy Desktop</h3>
<ul>
  <li><strong><a href="https://hypr.land/">Hyprland</a></strong> as a Wayland compositor</li>
  <li><strong><a href="https://github.com/Alexays/Waybar">Waybar</a></strong> for status bar</li>
  <li><strong><a href="https://github.com/SimplyCEO/wofi">Wofi</a></strong> for application launcher</li>
</ul>

<h3 id="macos-things">macOS Things</h3>
<ul>
  <li><strong><a href="https://ghostty.org/">Ghostty</a></strong> terminal</li>
</ul>

<h2 id="using-the-setup">Using the Setup</h2>

<p>Once you’ve cloned the repo to <code class="language-plaintext highlighter-rouge">~/dotfiles</code>, using it is straightforward:</p>

<p>Install just the essentials:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>stow zsh git nvim zellij
</code></pre></div></div>

<p>Full Arch Linux desktop:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>stow zsh git nvim zellij hyprland wayland-tools gtk lazygit yazi btop
</code></pre></div></div>

<p>macOS with development tools:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>stow zsh git nvim zellij macos-tools lazygit yazi btop ideavim
</code></pre></div></div>

<p>And if you want to remove something:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>stow <span class="nt">-D</span> nvim
</code></pre></div></div>

<h2 id="personal-scripts-and-secrets">Personal Scripts and Secrets</h2>

<p>I keep personal scripts and secrets in <code class="language-plaintext highlighter-rouge">~/.zsh_scripts/</code> and <code class="language-plaintext highlighter-rouge">~/.zsh_secrets/</code> respectively. These directories are gitignored, but any <code class="language-plaintext highlighter-rouge">.sh</code> files in them are automatically sourced by my zsh config. This gives me a place for machine-specific or sensitive configs that don’t belong in version control.</p>

<h2 id="the-payoff">The Payoff</h2>

<p>The real benefit of this setup became clear when I set up my Arch Linux machine. Instead of spending hours remembering which configs I needed and manually copying everything over, I just ran the install script and stowed the packages I wanted. Five minutes later, my terminal felt like home.</p>

<p>Same goes for when I inevitably mess something up. I can nuke my configs, re-stow, and be back up and running immediately. No hunting for backup files or trying to remember what I changed.</p>

<h2 id="try-it-out">Try It Out</h2>

<p>You can find my dotfiles at <a href="https://github.com/penkin/dotfiles">github.com/penkin/dotfiles</a>. Feel free to use them as reference or inspiration for your own setup.</p>

<p>If you’ve never used Stow before, I’d highly recommend giving it a shot. It’s one of those tools that just makes sense once you start using it.</p>

<h2 id="warning">Warning</h2>

<p>I am always playing with my dotfiles, tweaking them, and experimenting with new tools. This means that some of the configurations may not work perfectly out of the box, and you may need to make some adjustments to suit your needs.</p>]]></content><author><name>Christopher Penkin</name></author><category term="Development" /><category term="Tools" /><category term="Productivity" /><category term="Configuration" /><summary type="html"><![CDATA[How I use GNU Stow to manage dotfiles across macOS and Arch Linux. Simple symlink-based config management for zsh, neovim, and development tools.]]></summary></entry><entry><title type="html">Speeding up our .NET builds</title><link href="https://www.penkin.me/development/.net/2025/10/03/speed-up-dotnet-builds.html" rel="alternate" type="text/html" title="Speeding up our .NET builds" /><published>2025-10-03T00:00:00+00:00</published><updated>2025-10-03T00:00:00+00:00</updated><id>https://www.penkin.me/development/.net/2025/10/03/speed-up-dotnet-builds</id><content type="html" xml:base="https://www.penkin.me/development/.net/2025/10/03/speed-up-dotnet-builds.html"><![CDATA[<p>We’ve all been there; waiting for builds that seem to take forever, especially when you’re working on applications that mix .NET backend code and javascript/css assets. Every small change triggers a full rebuild of everything, including npm packages that haven’t changed in weeks.</p>

<p>Recently, a teammate introduced a simple but clever solution that cut our build times dramatically, a custom BUILD_CACHE property that intelligently decides when to rebuild frontend assets.</p>

<h2 id="the-problem">The Problem</h2>

<p>The project is an large monolith application with many .NET projects and quite a few of them need to build javascript and/or css assets.</p>

<p>The issue? Every build had expensive asset rebuilds. <code class="language-plaintext highlighter-rouge">npm ci</code> alone can take several minutes on a fresh install, and <code class="language-plaintext highlighter-rouge">npm run build</code> adds even more time for webpack to do its thing.</p>

<h2 id="the-solution">The Solution</h2>

<p>The fix was surprisingly elegant. Instead of always running the full npm build process, we created conditional MSBuild targets.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Target</span> <span class="na">Name=</span><span class="s">"PreBuild"</span> <span class="na">AfterTargets=</span><span class="s">"PreBuildEvent"</span> <span class="na">Condition=</span><span class="s">"'$(BUILD_CACHE)' != 'true'"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;Exec</span> <span class="na">Command=</span><span class="s">"npm ci"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;Exec</span> <span class="na">Command=</span><span class="s">"npm run build"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/Target&gt;</span>
<span class="nt">&lt;Target</span> <span class="na">Name=</span><span class="s">"PreBuildCached"</span> <span class="na">AfterTargets=</span><span class="s">"PreBuildEvent"</span> <span class="na">Condition=</span><span class="s">"'$(BUILD_CACHE)' == 'true'"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;Exec</span> <span class="na">Command=</span><span class="s">"node ../../if-changes.js"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/Target&gt;</span>
</code></pre></div></div>

<p>When <code class="language-plaintext highlighter-rouge">BUILD_CACHE=true</code>, instead of blindly rebuilding everything, we run a custom Node.js script called <code class="language-plaintext highlighter-rouge">if-changes.js</code> that intelligently checks what’s actually changed.</p>

<h2 id="how-it-works">How It Works</h2>

<p>The magic happens in the <code class="language-plaintext highlighter-rouge">package.json</code> configuration of the various projects. We add the following <code class="language-plaintext highlighter-rouge">onlyIfChanged</code> section to it:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"onlyIfChanged"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dependencies"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"globs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="s2">"package-lock.json"</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm ci"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"globs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="s2">"Scripts/**/*"</span><span class="p">,</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run build"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<p>The script uses file globbing (<strong>powered by fast-glob</strong>) to check specific files and directories, only when these files change does it trigger the associated commands. No more rebuilding frontend assets when you’ve only touched C# code.</p>

<h2 id="setting-it-up">Setting It Up</h2>

<p>Here is how to set it up on both <a href="https://www.jetbrains.com/rider/">Rider</a> and <a href="https://visualstudio.microsoft.com/">Visual Studio</a>.</p>

<h3 id="for-rider-users">For Rider users:</h3>

<p>Settings → Build, Execution, Deployment → Toolset and Build<br />
Edit <code class="language-plaintext highlighter-rouge">"MSBuild Global Properties"</code><br />
Add <code class="language-plaintext highlighter-rouge">BUILD_CACHE=true</code></p>

<h3 id="for-visual-studio-users">For Visual Studio users:</h3>

<p>There is no built in way to do this in Visual Studio so we need to make use of environment variables within Windows.</p>

<p>Open System Properties → Advanced → Environment Variables<br />
Add <code class="language-plaintext highlighter-rouge">BUILD_CACHE=true</code> as a system variable<br />
Restart your computer <strong>(yes, really!)</strong></p>

<p>Add the conditional targets to your <code class="language-plaintext highlighter-rouge">.csproj</code> file shown above and configure the <code class="language-plaintext highlighter-rouge">onlyIfChanged</code> section in your <code class="language-plaintext highlighter-rouge">package.json</code> file. Don’t forget to install <a href="https://www.npmjs.com/package/fast-glob">fast-glob</a> as a dependency.</p>

<h2 id="the-results">The Results</h2>
<p>The speed improvement was immediate and significant. Local development builds that used to take 5-10 minutes now complete in 1 and developers stopped grabbing coffee during every build cycle (<strong>not sure if that is a win or a lose</strong>).</p>

<p>The results on my MacBook Pro with an M1 chip and 16GB of RAM. The gap was even more noticable on others running Windows… just saying :P</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>With cache    Build completed in 00:01:01.969
Without cache Build completed in 00:07:09.369
</code></pre></div></div>

<blockquote>
  <p>I’m reposting this one of the most underrated inventions of our time. I’ve gone from Mobi effectively not starting up on my machine anymore to it starting up within seconds. <strong>~ One happy customer in our slack channels</strong></p>
</blockquote>

<p>The best part? It’s completely transparent. Developers who haven’t set up the caching still get working builds, they just take longer. Those who enable caching get the speed benefits without any downsides.
Sometimes the best optimisations aren’t about fancy new tools or frameworks; they’re about being smart about when you choose to skip work entirely.</p>

<h2 id="bonus">Bonus</h2>

<p>Here is a link to the GitHub gist of our <a href="https://gist.github.com/penkin/1488fbc798a9c8fd21227d50d728440f">if-changes.js</a> file.</p>]]></content><author><name>Christopher Penkin</name></author><category term="Development" /><category term=".NET" /><summary type="html"><![CDATA[Learn how we reduced .NET build times from 7+ minutes to under 1 minute using a custom BUILD_CACHE property and smart file change detection for hybrid applications with frontend assets.]]></summary></entry><entry><title type="html">How I built this blog</title><link href="https://www.penkin.me/blog/jekyll/github-pages/2025/07/31/how-i-built-this-blog.html" rel="alternate" type="text/html" title="How I built this blog" /><published>2025-07-31T00:00:00+00:00</published><updated>2025-07-31T00:00:00+00:00</updated><id>https://www.penkin.me/blog/jekyll/github-pages/2025/07/31/how-i-built-this-blog</id><content type="html" xml:base="https://www.penkin.me/blog/jekyll/github-pages/2025/07/31/how-i-built-this-blog.html"><![CDATA[<p>There are quite a few static site generators that can be used to build a quick little blog and thought I’d share just how I built this little space on the internet. Setting up this blog took a weekend and most of that time was me playing with a design I liked that would work on all form factors as well as implementing a dark mode that respected the user’s selected system colour scheme. I probably could have easily used an existing theme but I wanted something that was more mine.</p>

<p>Below is a brief overview of the stack I used to build this blog.</p>

<h2 id="the-stack">The stack</h2>

<ul>
  <li><a href="#jekyll">Jekyll</a>
    <ul>
      <li><a href="#jekyll-paginate">Jekyll Paginate</a></li>
      <li><a href="#jekyll-feed">Jekyll Feed</a></li>
      <li><a href="#jekyll-sitemap">Jekyll Sitemap</a></li>
    </ul>
  </li>
  <li><a href="#tailwind-css">Tailwind CSS</a></li>
  <li><a href="#github-pages">GitHub Pages</a></li>
</ul>

<h2 id="jekyll">Jekyll</h2>

<p><a href="https://jekyllrb.com/">Jekyll</a> is a static site generator that can be used to build a static websites like this little blog. What makes it great is that you can create your content in something simple like <a href="https://en.wikipedia.org/wiki/Markdown">Markdown</a> and Jekyll will take care of the rest. It also has some nice little features that let you organise your files in ways that are easy to maintain. You can have various layout files to define the structure of your pages and posts, and you can use <a href="https://jekyllrb.com/docs/front-matter/">Front Matter</a> to define metadata for your pages and posts.</p>

<p>I chose Jekyll mainly because it seemed really simple to get started. The main purpose of this blog is to practise writing, I did not want to get bogged down with complex setups, CI/CD pipelines etc. Jekyll is a great fit especially when hosting the site within GitHub Pages as it has a very low barrier to entry. There is a <a href="https://www.ruby-lang.org/en/">Ruby</a> dependency but as a software developer I already had that installed.</p>

<p>The other great thing about Jekyll are the <a href="https://jekyllrb.com/docs/plugins/">plugins</a>. Below are the plugins that I used.</p>

<h3 id="jekyll-paginate">Jekyll Paginate</h3>

<p><a href="https://jekyllrb.com/docs/pagination/">Jekyll Paginate</a> is as simple as it sounds. It adds a <code class="language-plaintext highlighter-rouge">paginator</code> object to your page that you can then show a page of posts and add some navigation to move between the pages. You can then use <code class="language-plaintext highlighter-rouge">paginator.posts</code> to iterate through to show that page’s posts and <code class="language-plaintext highlighter-rouge">paginator.total_pages</code>, <code class="language-plaintext highlighter-rouge">paginator.previous_page</code> and <code class="language-plaintext highlighter-rouge">paginator.next_page</code> to construct your paging navigation.</p>

<p>To get pagination working you’ll need to include <code class="language-plaintext highlighter-rouge">jekyll-paginate</code> in your <code class="language-plaintext highlighter-rouge">Gemfile</code> and add the following to your <code class="language-plaintext highlighter-rouge">_config.yml</code> file where <code class="language-plaintext highlighter-rouge">paginate</code> is the number of items you want on the page and <code class="language-plaintext highlighter-rouge">paginate_path</code> is the URL path to the page.</p>
<figure class="highlight"><pre><code class="language-yml" data-lang="yml"><span class="na">paginate</span><span class="pi">:</span> <span class="m">10</span>
<span class="na">paginate_path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/blog/page:num/"</span>

<span class="na">plugins</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">jekyll-paginate</span></code></pre></figure>
<blockquote>
  <p>Note that pagination only works in <code class="language-plaintext highlighter-rouge">html</code> files and not <code class="language-plaintext highlighter-rouge">markdown</code> files.</p>
</blockquote>

<h3 id="jekyll-feed">Jekyll Feed</h3>

<p><a href="https://github.com/jekyll/jekyll-feed">Jekyll Feed</a> adds all that Atom/RSS magic to the blog so that readers can read your posts from the comfort of their own feed readers.</p>

<p>Again a pretty simple setup that has you adding <code class="language-plaintext highlighter-rouge">jekyll-feed</code> to your <code class="language-plaintext highlighter-rouge">Gemfile</code> file. The config is also pretty straightforward. All I did was to update the icon with the following:</p>
<figure class="highlight"><pre><code class="language-yml" data-lang="yml"><span class="na">feed</span><span class="pi">:</span>
  <span class="na">icon</span><span class="pi">:</span> <span class="s">/assets/img/favicon-192x192.png</span>

<span class="na">plugins</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">jekyll-feed</span></code></pre></figure>
<p>There are a lot more config options available but I kept mine simple with the above but you can <a href="https://github.com/jekyll/jekyll-feed?tab=readme-ov-file#usage">see the rest here</a>.</p>

<h3 id="jekyll-sitemap">Jekyll Sitemap</h3>

<p><a href="https://github.com/jekyll/jekyll-sitemap">Jekyll Sitemap</a> helps with all that search engine love. I added it to generate a sitemaps.org compliant sitemap to help with indexing the site on various search engines.</p>

<p>Oh and guess what? That’s right! Add <code class="language-plaintext highlighter-rouge">jekyll-sitemap</code> to your <code class="language-plaintext highlighter-rouge">Gemfile</code> file and in the config you would add the <code class="language-plaintext highlighter-rouge">url</code> and the plugin name to the <code class="language-plaintext highlighter-rouge">plugins</code> section.</p>
<figure class="highlight"><pre><code class="language-yml" data-lang="yml"><span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://www.penkin.me"</span>

<span class="na">plugins</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">jekyll-sitemap</span></code></pre></figure>
<h2 id="tailwind-css">Tailwind CSS</h2>

<p><a href="https://tailwindcss.com/">Tailwind CSS</a> is a beautiful composable CSS framework that allows you to build your design within HTML. When I initially planned to start blogging again I wanted to also make this a little space that I can play around in. One of those things was to play more with Tailwind as well as implement dark mode abilities within the site and whatever else catches my fancy in the future.</p>

<p>Tailwind has some <a href="https://tailwindcss.com/docs/installation/tailwind-cli">comprehensive getting started guides</a> on how to use it within your site or app.</p>

<p>What I did to keep things simple within this blog is to simply add the following build scripts within my <code class="language-plaintext highlighter-rouge">package.json</code> file:</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"build:dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npx @tailwindcss/cli -i ./src/css/main.css -o ./assets/css/main.min.css --watch"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"build:prod"</span><span class="p">:</span><span class="w"> </span><span class="s2">"NODE_ENV=production npx @tailwindcss/cli -i ./src/css/main.css -o ./assets/css/main.min.css --minify"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span></code></pre></figure>
<p>All it’s doing is taking my input CSS file and outputting it to the assets folder after tailwind does its magic. I have not set up any CI/CD pipelines to automate the build of the CSS currently as it almost never changes. I wanted to focus on the writing aspect of this site and if I change any styles I simply run the <code class="language-plaintext highlighter-rouge">npm run build:dev</code> while I build. Once I am happy I run <code class="language-plaintext highlighter-rouge">npm run build:prod</code> and commit the file.</p>

<h2 id="github-pages">GitHub Pages</h2>

<p><a href="https://pages.github.com/">GitHub Pages</a> is a free static site hosting service offered by <a href="https://github.com/">GitHub</a>. Nothing beats free and they also have some <a href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll">Jekyll is fully supported</a> for simple little sites like this one.</p>

<p>Since I am hosting within GitHub Pages, I added the following to the <code class="language-plaintext highlighter-rouge">Gemfile</code>, <code class="language-plaintext highlighter-rouge">gem "github-pages", group: :jekyll_plugins</code>. The <code class="language-plaintext highlighter-rouge">github-pages</code> gem ensures my local development environment matches exactly what GitHub Pages will build, preventing the frustrating situation where my site works perfectly locally but breaks when deployed.</p>

<p>GitHub Pages does have a default set of plugins that it supports out of the box that helps you get setup with no mess or fuss. I believe though if you do want to go outside of these defaults that there is some additional setup that is required. I did not need anything outside of what I have above so it all just worked.</p>

<h2 id="whats-next">What’s Next?</h2>

<p>Next I guess is writing. The plan is to focus on getting one post out every month, if there are more, great, if not, fine. The setup is simple so that I can get that done. If I see some CSS or JS stuff I want to try I can also play with that here. Time will tell.</p>

<p>For now though… write. Commit. Push. Done!</p>

<p>You can see all the code at <a href="https://github.com/penkin/penkin.github.io">https://github.com/penkin/penkin.github.io</a>.</p>]]></content><author><name>Christopher Penkin</name></author><category term="blog" /><category term="jekyll" /><category term="github-pages" /><summary type="html"><![CDATA[Build a Jekyll blog with Tailwind CSS and GitHub Pages hosting in one weekend. Simple setup guide with pagination, RSS feeds, and dark mode.]]></summary></entry><entry><title type="html">Why I can’t use Dia over Arc</title><link href="https://www.penkin.me/browsers/arc/dia/zen/2025/07/10/arc-to-dia.html" rel="alternate" type="text/html" title="Why I can’t use Dia over Arc" /><published>2025-07-10T00:00:00+00:00</published><updated>2025-07-10T00:00:00+00:00</updated><id>https://www.penkin.me/browsers/arc/dia/zen/2025/07/10/arc-to-dia</id><content type="html" xml:base="https://www.penkin.me/browsers/arc/dia/zen/2025/07/10/arc-to-dia.html"><![CDATA[<blockquote>
  <p><strong>UPDATE:</strong> I more recently wrote about how my opinion on Dia has changed. See my updated thought <a href="https://www.penkin.me/browsers/arc/dia/2026/01/09/arc-to-dia-revisited.html">here</a>.</p>
</blockquote>

<p>It’s wild to me that <a href="https://thebrowser.company/">The Browser Company</a> took a browser with an almost cult following to just drop it completely to follow what was apparently their initial vision. It leaves me wondering though, if <a href="https://arc.net/">Arc</a> was not the original vision did they perhaps stumble into something that was too good to be true?</p>

<p>When I was first introduced to Arc I did not think the hype was real. Surely a browser is a browser is a browser… right? Nope! Arc just felt right. It had lots of little hidden gems that just worked well and that first install; sublime! I still remember selling it so hard at work, getting everyone to try it and many of them loved it too.</p>

<p>I have to say that I was a bit disappointed when I first tried <a href="https://www.diabrowser.com/">Dia</a>. It felt like I was just using Chrome again but had some AI secret sauce. What I loved in Arc is just not there in Dia and it’s just too much that I can’t switch over to Dia.</p>

<p>Even one of the people I got onto Arc initially also tried Dia and soon as he saw he needed a whole new account to use it he just didn’t even bother. The friction was too high.</p>

<p>So understand what I write below is not to smear Dia but to highlight the little things in Arc that I just cannot live without and ultimately leaves Dia packed away in my Applications folder, never getting its time to shine.</p>

<h2 id="spaces">Spaces</h2>

<p>The thing that I probably hate the most is how spaces work in Dia as apposed to Arc. Arc’s spaces are super simple to navigate between with the mouse. I generally have four spaces that I keep everything separate in. I have work, clients, personal etc. I can easily switch between them in Arc and it has a single window when I switch.</p>

<figure>
    <img src="https://www.penkin.me/assets/img/dia-spaces.png" alt="Dia browser showing its spaces." />
    <figcaption>Fig1. - Dia spaces</figcaption>
</figure>

<p>Dia on the other hand shows a new window per space (<strong>profile</strong>)?! I find it clunky to navigate with the mouse because I have to go to the little colour space button, click it and select the space I want to go to; even the keyboard shortcut shows the profiles menu then over to the arrow keys. Nope, nope, nope! With Arc I can be anywhere in the sidebar and hit the left and right buttons on my mouse to navigate between spaces. I think this is my No. 1 worst change between Dia and Arc.</p>

<h2 id="sidebar">Sidebar</h2>

<figure>
    <img src="https://www.penkin.me/assets/img/dia-tabs.png" alt="Dia browser without a sidebar." />
    <figcaption>Fig2. - Dia without sidebar</figcaption>
</figure>

<p>Until <a href="https://www.youtube.com/watch?v=5vexXIjRbyQ">two weeks ago</a> Dia had no sidebar, it was back to tabs only at the top. That is neither here nor there really but initially I missed the option to have it how I got used to using a browser in Arc. Even though it’s not a massive deal it’s an interesting choice when you are hoping to attract your existing user base, was it to clearly indicate that they’re not Arc anymore?</p>

<p>I’m glad however that the sidebar is now there but it’s really limited. I mean really really REALLY limited.</p>

<figure>
    <img src="https://www.penkin.me/assets/img/dia-sidebar.png" alt="Dia browser with a sidebar." />
    <figcaption>Fig3. - Dia with sidebar</figcaption>
</figure>

<p>So, what are these limitations, you ask? Folders and an extension on that; live folders. There is no way to organise your tabs which I used a lot in my dev work. So none of the bells and whistles that were once there, basically just the tabs at the top can now be on the side.</p>

<h3 id="folders">Folders</h3>

<figure>
    <img src="https://www.penkin.me/assets/img/arc-folders.png" alt="Arc browser with folders." />
    <figcaption>Fig4. - Arc with folders</figcaption>
</figure>

<p>Depending on the client I’m working with, they often have multiple environments and folders makes it super simple to organise all of that. Do I need to test something in QA, it’s all there in the QA folder. UAT? Oh yes, right there in the UAT folder. Dia just does not have the ability to do that.</p>

<p>The <strong>“same thing”</strong> in Dia is just the normal bookmarks bar from 1993.</p>

<figure>
    <img src="https://www.penkin.me/assets/img/dia-bookmarks.png" alt="Dia browser showing bookmarks bar." />
    <figcaption>Fig5. - Dia bookmarks bar</figcaption>
</figure>

<h3 id="live-folders">Live folders</h3>

<figure>
    <img src="https://www.penkin.me/assets/img/github-live-folder.png" alt="Arc browser with GitHub live folder." />
    <figcaption>Fig6. - Arc GitHub live folder</figcaption>
</figure>

<p>As a developer, I found the <a href="https://github.com">GitHub</a> live folders almost too useful. The live folder would automagically update with any new pull requests that were open and assigned to me. No need to check mails for new requests coming in, or going to GitHub to check. Nope, all just there in all its glory. Guess what; again; Dia does not have this feature.</p>

<h2 id="tab-splits">Tab Splits</h2>

<figure>
    <img src="https://www.penkin.me/assets/img/arc-split-tabs.png" alt="Arc browser with split tabs." />
    <figcaption>Fig7. - Arc split tabs</figcaption>
</figure>

<p>Being able to put tabs side by side or below each other was something I use in Arc. I found it useful when I was working on some UI changes to have the Figma design next to the working tab to compare the two. Again Dia does not have this feature.</p>

<h2 id="copy-url">Copy URL</h2>

<figure>
    <img src="https://www.penkin.me/assets/img/copy-url.png" alt="Arc browser copy URL button." />
    <figcaption>Fig8. - Arc copy URL button</figcaption>
</figure>

<p>This is such a tiny thing but I think it just shows that they were not exaggerating when saying they were starting Dia from the ground up. They left out all the cool things about Arc right down to the little copy URL button in the URL bar. Just everything is gone and it’s just a Chrome browser with AI chat built in that can look at all my tabs and I guess help me with all that?</p>

<h2 id="love-letter">Love Letter</h2>

<p>So again I am not writing this to bash Dia but rather as a love letter from an Arc user to just say Arc was incredible for all the little things it had and how it made working in a browser that much easier. Dia is just not that. Maybe when testing with college students it makes sense because as they study they may have many tabs open on subjects that they need summarised and organised etc. Maybe Dia is good at that but I have tried and I can’t see how Dia is better than Arc in how it has gotten me to use my browser. It’s just too much friction for me to move to Dia.</p>

<p>I think the other concern I have is that with Dia the primary love I see Arc getting is mostly around security fixes and brining in the latest <a href="https://developer.chrome.com/docs/chromium">Chromium</a>, which I am grateful for, but it also means with Dia that Arc will stagnate quickly. There is hope though on the horizon in the shape of the <a href="https://zen-browser.app/">Zen browser</a>.</p>

<h2 id="zen-browser">Zen Browser</h2>

<figure>
    <img src="https://www.penkin.me/assets/img/zen.png" alt="Zen browser." />
    <figcaption>Fig9. - Zen browser</figcaption>
</figure>

<p>They have started implementing a lot of the features from Arc using <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> as the base. They don’t have folders or live folders yet but it is being worked on <a href="https://github.com/zen-browser/desktop/pull/9355">here</a> and <a href="https://github.com/zen-browser/desktop/pull/8888">here</a>. It’s available to <a href="https://zen-browser.app/download/">download</a> on Windows, MacOS and Linux. Be keen to see how it turns out and a pro if it does replace Arc for me is that I can use it on my Linux machine as well.</p>]]></content><author><name>Christopher Penkin</name></author><category term="browsers" /><category term="arc" /><category term="dia" /><category term="zen" /><summary type="html"><![CDATA[Comparing Arc and Dia browsers from a developer's perspective—why Arc’s thoughtful features like spaces, folders, live integrations, and split tabs make Dia unattractive.]]></summary></entry><entry><title type="html">AI in Software Development</title><link href="https://www.penkin.me/ai/development/mcp/agentic%20ai/2025/07/07/ai-in-software-development.html" rel="alternate" type="text/html" title="AI in Software Development" /><published>2025-07-07T00:00:00+00:00</published><updated>2025-07-07T00:00:00+00:00</updated><id>https://www.penkin.me/ai/development/mcp/agentic%20ai/2025/07/07/ai-in-software-development</id><content type="html" xml:base="https://www.penkin.me/ai/development/mcp/agentic%20ai/2025/07/07/ai-in-software-development.html"><![CDATA[<p>AI has been transforming software development in profound and practical ways. During a recent 30-minute talk at 
<a href="https://www.digitalsolutionfoundry.co.za/">Digital Solution Foundry</a>, I shared an overview of where we’re at with 
AI in coding, some tools and paradigms that are emerging, and how we, as developers, can understand and leverage 
these evolving capabilities.</p>

<h2 id="heres-what-we-covered">Here’s what we covered:</h2>

<ol>
  <li>AI and Software Development</li>
  <li>What Agentic Development Means</li>
  <li>Introduction to the Model Context Protocol (MCP)</li>
  <li>Live Demo</li>
</ol>

<h2 id="ai-powered-development-tools">AI-Powered Development Tools</h2>

<p>Today’s developers are spoiled for choice when it comes to AI-enhanced tools. Whether you’re writing boilerplate
code, critiquing code, or generating tests, AI is finding its way into every dev workflow.</p>

<p>Here are just a few tools to know:</p>

<ul>
  <li><a href="https://github.com/features/copilot">GitHub Copilot</a>: Needs no introduction at this point.</li>
  <li><a href="https://cursor.com/">Cursor</a>: Offers rich inline suggestions, feels snappy and intuitive.</li>
  <li><a href="https://windsurf.com/">Windsurf</a>: Another AI-driven environment built off Visual Studio Code.</li>
  <li><a href="https://www.jetbrains.com/ai/">JetBrains AI</a>: Integrated assistant for IntelliJ-based IDEs.</li>
  <li><a href="https://zed.dev/">Zed</a>: Lightweight, with an AI co-dev baked in.</li>
</ul>

<p>All of these tools offer variations of:</p>

<ul>
  <li>Autocomplete magic</li>
  <li>Automated boilerplate generation</li>
  <li>Generating tests with almost no mental overhead</li>
</ul>

<p>Ultimately, AI has the potential of making developers faster and more efficient—turning high-level ideas into 
runnable code more quickly than ever.</p>

<h2 id="so-what-is-agentic-ai">So, What is “Agentic AI”?</h2>

<p>“Agentic” AI refers to systems capable of taking initiative and making decisions on their own. You don’t just 
command them step-by-step — they can autonomously plan and act.</p>

<p>An agentic AI doesn’t just respond; it draws from its internal model, gets context, plans a task, and implements 
the code — end-to-end.</p>

<h2 id="however-theres-a-big-caveat">However, there’s a big caveat.</h2>

<p>Agents are only as good as the access and instructions you give them. If the inputs are unclear or the data and 
scope are limited, you’ll get unclear or incorrect outputs.</p>

<blockquote>
  <p><a href="https://substack.com/@kentbeck">Kent Beck</a> calls it his “genie.” You get exactly what you asked for, not what 
you meant to ask for.</p>
</blockquote>

<h2 id="what-is-the-model-context-protocol-mcp">What is the Model Context Protocol (MCP)?</h2>

<p>Here’s where things get really interesting.</p>

<p>The Model Context Protocol (MCP) is shaping up to be a powerful paradigm in the AI workflow. MCP allows AI 
models to talk to tools via structured, documented interfaces.</p>

<figure>
	<img src="https://www.penkin.me/assets/img/mcp.png" alt="" />
	<figcaption>Fig1. - MCP overview</figcaption>
</figure>

<p>Think of it like plug-and-play for tools + AI. We’re starting to get into the realms of this being our very own
Jarvis for Ironman. You can take something like <a href="https://claude.ai/download">Claude Desktop</a> and connect it to 
various tools to create a personal assistant that knows about your data and can act on your behalf.</p>

<p>I’m not sure how ready I am for this step to be frank but the thought of being able to talk to an AI that knows
my calendar, my mails, my <a href="https://www.notion.so/">Notion</a> documents… has my nerdy senses tingling. Who knows, maybe
my memory is short since it was not that long ago I wrote about leaving 
<a href="https://www.penkin.me/privacy/social%20media/2019/02/28/facebook-free.html">Facebook</a> and 
<a href="https://www.penkin.me/privacy/social%20media/2019/01/23/google-free.html">Google</a> behind.</p>

<p><a href="https://hub.docker.com/mcp">Docker</a> is doing something pretty interesting here, they are creating a hub for MCPs that
allow you to run contanerised versions of these tools. So you can install one for Postgres and/or Graphana and/or GitHub and 
then chat to your AI about your data in these external apps.</p>

<p>I still want to play around with Notion’s MCP server and see how that pans out. I’m imagining that I can use Claude to add to
my notes, summarise them etc. Really keen to see where this all goes.</p>

<p>This isn’t just chat-based prompts anymore. It’s AI with real-world integrations, acting like a personal agent across all your services.</p>

<h2 id="final-thoughts">Final thoughts</h2>

<p>I really love where this is going so far, it’s really interesting using and playing with all these shiny toys. They are however
still in flux. So keep playing, don’t tie into any one service. They are all improving and evolving. Try things out and 
see where it takes you.</p>]]></content><author><name>Christopher Penkin</name></author><category term="AI" /><category term="Development" /><category term="MCP" /><category term="Agentic AI" /><summary type="html"><![CDATA[Exploring AI's impact on software development—from cutting-edge tools like GitHub Copilot and Cursor to agentic AI and the emerging Model Context Protocol (MCP).]]></summary></entry></feed>