<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>MoskitoHero</title><link>/</link><description>Recent content on MoskitoHero</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 17 Jul 2026 06:55:18 +0200</lastBuildDate><atom:link href="/index.xml" rel="self" type="application/rss+xml"/><item><title>Run Pasted Multiline Backslash-Escaped Shell Commands in Nushell</title><link>/post/2026-07-17-run-pasted-multiline-backslash-escaped-shell-commands-in-nushell/</link><pubDate>Fri, 17 Jul 2026 06:55:18 +0200</pubDate><guid>/post/2026-07-17-run-pasted-multiline-backslash-escaped-shell-commands-in-nushell/</guid><description>I often get frustrated when copy-pasting commands from a website into my Nushell terminal. Here is a little snippet to solve that issue.</description><content:encoded><![CDATA[<div class="paragraph">
<p>As a <a href="https://www.nushell.sh/">Nushell</a> user, I often get frustrated when copy-pasting commands from a website into my terminal. The typical use-case I encounter is the Github CI spitting out the failing tests with a command I can run in my terminal, like this one:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell">bundle <span class="nb">exec </span>rspec <span class="se">\</span>
  <span class="s1">&#39;./spec/requests/api/v1/campaigns/show_spec.rb[1:1:1:1:1]&#39;</span> <span class="se">\</span>
  <span class="s1">&#39;./spec/requests/api/v1/campaigns/show_spec.rb[1:1:1:2]&#39;</span> <span class="se">\</span>
  <span class="s1">&#39;./spec/requests/api/v1/campaigns/show_spec.rb[1:1:1:3:1]&#39;</span> <span class="se">\</span>
  <span class="s1">&#39;./spec/requests/api/v1/campaigns/show_spec.rb[1:1:1:4:1]&#39;</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Running this in nushell will give you an error like:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="rust"><span class="n">Error</span><span class="p">:</span> <span class="nn">nu</span><span class="p">::</span><span class="nn">parser</span><span class="p">::</span><span class="n">parse_mismatch</span>

  <span class="err">×</span> <span class="n">Parse</span> <span class="n">mismatch</span> <span class="n">during</span> <span class="n">operation</span><span class="err">.</span>
   <span class="err">╭─</span><span class="p">[</span><span class="n">repl_entry</span> <span class="err">#</span><span class="mi">3</span><span class="p">:</span><span class="mi">2</span><span class="p">:</span><span class="mi">62</span><span class="p">]</span>
 <span class="mi">1</span> <span class="err">│</span> <span class="n">bundle</span> <span class="n">exec</span> <span class="n">rspec</span> <span class="err">\</span>
 <span class="mi">2</span> <span class="err">│</span>   <span class="err">&#39;.</span><span class="o">/</span><span class="n">spec</span><span class="o">/</span><span class="n">requests</span><span class="o">/</span><span class="n">api</span><span class="o">/</span><span class="n">v1</span><span class="o">/</span><span class="n">campaigns</span><span class="o">/</span><span class="n">show_spec</span><span class="py">.rb</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="mi">1</span><span class="p">:</span><span class="mi">1</span><span class="p">:</span><span class="mi">1</span><span class="p">:</span><span class="mi">1</span><span class="p">]</span><span class="err">&#39;</span> <span class="err">\</span>
   <span class="err">·</span>                                                              <span class="err">┬</span>
   <span class="err">·</span>                                                              <span class="err">╰──</span> <span class="n">expected</span> <span class="n">operator</span>
 <span class="mi">3</span> <span class="err">│</span>   <span class="err">&#39;.</span><span class="o">/</span><span class="n">spec</span><span class="o">/</span><span class="n">requests</span><span class="o">/</span><span class="n">api</span><span class="o">/</span><span class="n">v1</span><span class="o">/</span><span class="n">campaigns</span><span class="o">/</span><span class="n">show_spec</span><span class="py">.rb</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="mi">1</span><span class="p">:</span><span class="mi">1</span><span class="p">:</span><span class="mi">2</span><span class="p">]</span><span class="err">&#39;</span> <span class="err">\</span>
   <span class="err">╰────</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>This happens because <code>\</code> is <a href="https://github.com/nushell/nushell/issues/4044">not recognized as an escape character in Nushell</a>.</p>
</div>
<div class="paragraph">
<p>The temporary workaround I found was to drop to zsh and run the command. But it was not an acceptable, long-term solution.</p>
</div>
<div class="paragraph">
<p>So here is <a href="https://github.com/nushell/nushell/issues/4044#issuecomment-3352257312">a little snippet I found on Github</a> to solve that issue. I added this to my <code>config.nu</code> file, in the <code>keybindings</code> array:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="rust"><span class="p">{</span>
  <span class="n">name</span><span class="p">:</span> <span class="s">&#34;replace_slash_line_break_and_run&#34;</span>
  <span class="n">modifier</span><span class="p">:</span> <span class="s">&#34;control&#34;</span>
  <span class="n">keycode</span><span class="p">:</span> <span class="s">&#34;enter&#34;</span>
  <span class="n">mode</span><span class="p">:</span> <span class="p">[</span><span class="n">emacs</span><span class="p">,</span> <span class="n">vi_normal</span><span class="p">,</span> <span class="n">vi_insert</span><span class="p">]</span>
  <span class="n">event</span><span class="p">:</span> <span class="p">[</span>
    <span class="p">{</span>
      <span class="n">send</span><span class="p">:</span> <span class="n">executehostcommand</span><span class="p">,</span>
      <span class="n">cmd</span><span class="p">:</span> <span class="err">&#39;</span>
        <span class="k">mut</span> <span class="n">cmd</span> <span class="o">=</span> <span class="p">(</span><span class="n">commandline</span><span class="p">)</span>
        <span class="k">let</span> <span class="err">$</span><span class="n">line_break_regex</span> <span class="o">=</span> <span class="err">&#39;\\\</span><span class="n">n</span><span class="err">&#39;</span>
        <span class="err">$</span><span class="n">cmd</span> <span class="o">=</span> <span class="err">$</span><span class="n">cmd</span> <span class="p">|</span> <span class="nb">str</span> <span class="n">replace</span> <span class="o">--</span><span class="n">all</span> <span class="o">--</span><span class="n">regex</span> <span class="err">$</span><span class="n">line_break_regex</span> <span class="err">&#39;&#39;</span>
        <span class="n">commandline</span> <span class="n">edit</span> <span class="err">$</span><span class="s">&#34;($cmd)&#34;</span> <span class="o">--</span><span class="n">accept</span>
      <span class="err">&#39;</span>
    <span class="p">},</span>
  <span class="p">]</span>
<span class="p">}</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>So now, I just paste the multiline snippet, hit <code>control-enter</code> and voilà!</p>
</div>
<div class="paragraph">
<p>All credits go to <a href="https://github.com/rondymesquita">Rondimesquita</a></p>
</div>
]]></content:encoded></item><item><title>How I Use JuJutsu Revisions to Plan My Work Ahead</title><link>/post/2026-06-30-how-i-use-jj-revisions-to-plan-my-work/</link><pubDate>Tue, 30 Jun 2026 09:29:19 +0200</pubDate><guid>/post/2026-06-30-how-i-use-jj-revisions-to-plan-my-work/</guid><description>JuJutsu VCS gives me planning superpowers. Here is how I use its revisions and flexible workflow to plan my work ahead and keep track of my progress.</description><content:encoded><![CDATA[<div class="paragraph">
<p><a href="https://www.jj-vcs.dev/latest/">JuJutsu</a> is a distributed version control system that allows you to manage your codebase and collaborate with others. One
of its features is the ability to create revisions, which are snapshots of your code at a specific point in time. In
this post, I will share how I use JuJutsu revisions to plan my work ahead.</p>
</div>
<div class="paragraph">
<p>When I start working on a new ticket, I usually create a new revision for it, with the ticket number and description,
along with a bookmark, like I described <a href="https://moskitohero.com/post/2026-06-23-nujutsu-custom-jujutsu-commands-in-nushell-with-a-neovim-twist">in my previous post</a>.</p>
</div>
<div class="paragraph">
<p>The next step is to start planning my work ahead. And JuJutsu makes it easier by allowing me to split my ticket into
multiple sub-tasks, each matched to its own revision. That way, I can start working on one sub-task, then the next one,
and so on.</p>
</div>
<div class="paragraph">
<p>This is what it can look like in practice:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="/assets/img/jj-planning-ahead.png" alt="Planning Ahead with JuJutsu Revisions"/>
</div>
</div>
<div class="paragraph">
<p>Keeping that habit is very useful because it forces me to think things through before I start coding. When working with
an agent, it also gives it the context of the piece of work I am doing, and allows it to focus on the right sub-task,
instead of trying to solve the whole ticket at once.</p>
</div>
<div class="paragraph">
<p>What often happens it that when editing a revision, I realize that I have to change something I had not anticipated in
the previous one. In that case, I just go back to it and edit it. If that change is important and requires a lot of work,
I usually create a new revision on top of the previous one, and squash it once I am happy with the result.</p>
</div>
<div class="paragraph">
<p>The icing on the JJ cake is that if those changes lead up to conflicts, <a href="https://www.jj-vcs.dev/latest/conflicts/">they do not block me</a>. I can follow my train of
thought, and resolve the conflicts later, when I have more time / energy to devote to it.</p>
</div>
<div class="paragraph">
<p>Once everything is done, I can squash all the revisions into one, or keep a few of them as logical milestones in my work
if I want to keep track of the progress I made.</p>
</div>
]]></content:encoded></item><item><title>Nujutsu Custom Jujutsu Commands in Nushell (With a Neovim Twist)</title><link>/post/2026-06-23-nujutsu-custom-jujutsu-commands-in-nushell-with-a-neovim-twist/</link><pubDate>Tue, 23 Jun 2026 06:40:13 +0200</pubDate><guid>/post/2026-06-23-nujutsu-custom-jujutsu-commands-in-nushell-with-a-neovim-twist/</guid><description>A couple of small Nu functions I use daily at work to make my life easier with JuJutsu and Nushell.</description><content:encoded><![CDATA[<div class="paragraph">
<p>I use <a href="https://www.nushell.sh">Nushell</a> as my main shell. I love it for the UX, the UI, and the syntax. I do sometimes hate it for not being POSIX, but since that’s the whole point of the thing, I cannot really complain.</p>
</div>
<div class="paragraph">
<p>I also use <a href="https://www.jj-vcs.dev/latest/">JuJutsu</a> as my main VCS. I love it for the UX, the UI, and the commands.</p>
</div>
<div class="paragraph">
<p>Say what you want, I’m a tech romantic.</p>
</div>
<div class="paragraph">
<p>So let me share a little bit of <strong>NuJutsu</strong> love with those two small Nu functions I use daily at work:</p>
</div>
<div class="sect1">
<h2 id="jj-desc"><a class="anchor" href="#jj-desc"></a>jj-desc</h2>
<div class="sectionbody">
<div class="paragraph">
<p>This one interactively asks me for the JIRA ticket number, and the title of issue. It builds a formatted description out of it.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"> <span class="k">def</span> <span class="nf">jj</span><span class="o">-</span><span class="n">desc</span> <span class="p">[]</span> <span class="p">{</span>
    <span class="n">let</span> <span class="n">ticket</span> <span class="o">=</span> <span class="n">input</span> <span class="s2">&#34;Ticket number: &#34;</span>
    <span class="n">let</span> <span class="n">title</span> <span class="o">=</span> <span class="n">input</span> <span class="s2">&#34;Title: &#34;</span>
    <span class="n">jj</span> <span class="n">desc</span> <span class="o">-</span><span class="n">m</span> <span class="vg">$&#34;</span><span class="p">[(</span><span class="vg">$ticket</span><span class="p">)]</span> <span class="p">(</span><span class="vg">$title</span><span class="p">)</span><span class="s2">&#34;
  }
</span></code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="jj-bookmark-from-desc"><a class="anchor" href="#jj-bookmark-from-desc"></a>jj-bookmark-from-desc</h2>
<div class="sectionbody">
<div class="paragraph">
<p>This one creates a bookmark from the current JJ description — provided it uses the correct format specified above, so <code>[PROJ-12345] Add feature A to project C</code> will yield a bookmark like <code>feature/proj-12345-add-feature-a-to-project-c</code>.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="k">def</span> <span class="nf">jj</span><span class="o">-</span><span class="n">bookmark</span><span class="o">-</span><span class="n">from</span><span class="o">-</span><span class="n">desc</span> <span class="p">[]</span> <span class="p">{</span>
      <span class="n">let</span> <span class="n">desc</span> <span class="o">=</span> <span class="p">(</span><span class="n">jj</span> <span class="n">log</span> <span class="o">-</span><span class="n">r</span> <span class="err">@</span> <span class="o">--</span><span class="n">no</span><span class="o">-</span><span class="n">graph</span> <span class="o">-</span><span class="no">T</span> <span class="s1">&#39;description.first_line()&#39;</span><span class="p">)</span>
      <span class="n">let</span> <span class="n">bookmark</span> <span class="o">=</span> <span class="p">(</span><span class="vg">$desc</span>
        <span class="o">|</span> <span class="n">str</span> <span class="n">downcase</span>
        <span class="o">|</span> <span class="n">str</span> <span class="n">replace</span> <span class="o">--</span><span class="n">all</span> <span class="o">--</span><span class="n">regex</span> <span class="s1">&#39;[^a-z0-9\s-]&#39;</span> <span class="s1">&#39;&#39;</span>
        <span class="o">|</span> <span class="n">str</span> <span class="n">replace</span> <span class="o">--</span><span class="n">all</span> <span class="o">--</span><span class="n">regex</span> <span class="s1">&#39;\s+&#39;</span> <span class="s1">&#39;-&#39;</span>
        <span class="o">|</span> <span class="n">str</span> <span class="n">replace</span> <span class="o">--</span><span class="n">all</span> <span class="o">--</span><span class="n">regex</span> <span class="s1">&#39;-+&#39;</span> <span class="s1">&#39;-&#39;</span>
        <span class="o">|</span> <span class="n">str</span> <span class="n">trim</span> <span class="o">--</span><span class="n">right</span> <span class="o">--</span><span class="n">char</span> <span class="s1">&#39;-&#39;</span><span class="p">)</span>
      <span class="n">jj</span> <span class="n">bookmark</span> <span class="n">create</span> <span class="vg">$&#34;</span><span class="n">feature</span><span class="o">/</span><span class="p">(</span><span class="vg">$bookmark</span><span class="p">)</span><span class="s2">&#34;
    }</span></code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="the-icing-on-the-cake-neovim-counterparts"><a class="anchor" href="#the-icing-on-the-cake-neovim-counterparts"></a>The icing on the cake: Neovim counterparts</h2>
<div class="sectionbody">
<div class="paragraph">
<p>I use [jj.nvim] to manage my JJ log in Neovim.</p>
</div>
<div class="paragraph">
<p>Here is part of my LazyVim plugin definition:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="lua"><span class="k">return</span> <span class="p">{</span>
  <span class="s2">&#34;nicolasgb/jj.nvim&#34;</span><span class="p">,</span>
  <span class="n">branch</span> <span class="o">=</span> <span class="s2">&#34;main&#34;</span><span class="p">,</span>
  <span class="n">lazy</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
  <span class="n">config</span> <span class="o">=</span> <span class="k">function</span><span class="p">()</span>
    <span class="nb">require</span><span class="p">(</span><span class="s2">&#34;jj&#34;</span><span class="p">).</span><span class="n">setup</span><span class="p">({})</span>
    <span class="kd">local</span> <span class="n">cmd</span> <span class="o">=</span> <span class="nb">require</span><span class="p">(</span><span class="s2">&#34;jj.cmd&#34;</span><span class="p">)</span>
    <span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;ji&#34;</span><span class="p">,</span> <span class="k">function</span><span class="p">()</span>
      <span class="k">if</span> <span class="ow">not</span> <span class="n">utils</span><span class="p">.</span><span class="n">is_jj_repo</span><span class="p">()</span> <span class="k">then</span>
        <span class="n">vim</span><span class="p">.</span><span class="n">notify</span><span class="p">(</span><span class="s2">&#34;Not in a jj repository&#34;</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">log</span><span class="p">.</span><span class="n">levels</span><span class="p">.</span><span class="n">WARN</span><span class="p">)</span>
        <span class="k">return</span>
      <span class="k">end</span>
      <span class="n">vim</span><span class="p">.</span><span class="n">ui</span><span class="p">.</span><span class="n">input</span><span class="p">({</span> <span class="n">prompt</span> <span class="o">=</span> <span class="s2">&#34;Jira ticket: &#34;</span> <span class="p">},</span> <span class="k">function</span><span class="p">(</span><span class="n">ticket</span><span class="p">)</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">ticket</span> <span class="k">then</span> <span class="k">return</span> <span class="k">end</span>
        <span class="n">vim</span><span class="p">.</span><span class="n">ui</span><span class="p">.</span><span class="n">input</span><span class="p">({</span> <span class="n">prompt</span> <span class="o">=</span> <span class="s2">&#34;Title: &#34;</span> <span class="p">},</span> <span class="k">function</span><span class="p">(</span><span class="n">title</span><span class="p">)</span>
          <span class="k">if</span> <span class="ow">not</span> <span class="n">title</span> <span class="k">then</span> <span class="k">return</span> <span class="k">end</span>
          <span class="n">cmd</span><span class="p">.</span><span class="n">describe</span><span class="p">(</span><span class="nb">string.format</span><span class="p">(</span><span class="s2">&#34;[%s] %s&#34;</span><span class="p">,</span> <span class="n">ticket</span><span class="p">,</span> <span class="n">title</span><span class="p">))</span>
        <span class="k">end</span><span class="p">)</span>
      <span class="k">end</span><span class="p">)</span>
    <span class="k">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;JJ describe interactive&#34;</span> <span class="p">})</span>
    <span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;jc&#34;</span><span class="p">,</span> <span class="k">function</span><span class="p">()</span>
      <span class="k">if</span> <span class="ow">not</span> <span class="n">utils</span><span class="p">.</span><span class="n">is_jj_repo</span><span class="p">()</span> <span class="k">then</span>
        <span class="n">vim</span><span class="p">.</span><span class="n">notify</span><span class="p">(</span><span class="s2">&#34;Not in a jj repository&#34;</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">log</span><span class="p">.</span><span class="n">levels</span><span class="p">.</span><span class="n">WARN</span><span class="p">)</span>
        <span class="k">return</span>
      <span class="k">end</span>
      <span class="n">vim</span><span class="p">.</span><span class="n">cmd</span><span class="p">(</span><span class="s2">&#34;!nu -lc jj-bookmark-from-desc&#34;</span><span class="p">)</span>
    <span class="k">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;JJ bookmark from desc&#34;</span> <span class="p">})</span>
  <span class="k">end</span><span class="p">,</span>
<span class="p">}</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>The <code>jj-desc</code> command is rewritten in lua and gets a nice UX with two prompts popping up in your face when you launch it.</p>
</div>
<div class="paragraph">
<p>The <code>jj-bookmark-from-desc</code> command is just a plain keybinding.</p>
</div>
<div class="paragraph">
<p>In Emacs, I tend to open Vterm more often, so I just use my custom Nushell functions from there.</p>
</div>
</div>
</div>
]]></content:encoded></item><item><title>My Dream Ide Does Not Exist</title><link>/post/2026-06-16-my-dream-ide-does-not-exist/</link><pubDate>Tue, 16 Jun 2026 06:52:46 +0200</pubDate><guid>/post/2026-06-16-my-dream-ide-does-not-exist/</guid><description>Random thoughts about my erratic / frenetic search for the perfect IDE, and why I keep switching between Emacs, Neovim, Zed (and Helix). What do I even want?</description><content:encoded><![CDATA[<div class="paragraph">
<p><a href="https://www.gnu.org/software/emacs/">Emacs</a> is my main IDE. It is powerful, versatile, and very stable. It has a few limitations I can live with,
like the absence of multi-threading, which sometimes gets in the way on rare occasions. Native compilation gave it
wings and the <a href="https://github.com/doomemacs">Doom configuration layer</a> enables an improved UX, great keybindings and sane defaults. Did I mention Org,
and more particularly <a href="https://www.orgroam.com">Org-Roam</a>?</p>
</div>
<div class="paragraph">
<p>On a side note, <code>Elisp (is a (nightmare (of brackets (you) need to (follow through) (and count) (which (I suck at)))))</code> — more on that in another post about programming language UX and why I love Ruby. But in the end, I can live with a few crashes here
and there as I get lost in the bracket maze when editing my <code>config.el</code>. I can count.</p>
</div>
<div class="paragraph">
<p>That said, there is <strong>some</strong> friction involved when using Emacs. MacOS integration is not perfect, especially when it
comes to managing the emacs daemon. I cannot make <a href="https://github.com/svaante/dape">Dape</a> (the DAP mode for emacs) work with ruby. I also find the UI to be
a little slow: typing has a tiny slight delay which kind of gets annoying in a subtle way. And that slaps you in
your face when you open <a href="https://neovim.io">Neovim</a>.</p>
</div>
<div class="paragraph">
<p>I’m a TUI nerd, so Neovim is one of the alternatives I turn to when I get bored or annoyed at Emacs. Like Emacs with Doom,
Neovim has the <a href="https://www.lazyvim.org">LazyVim</a> configuration layer to ease most of that side of things. Lua has a much better UX than Elisp, and
man, the whole beast is <strong>blazing fast</strong>, both in terms of loading time, overall snappiness, and typing rendering. Some
plugins are really better than the Emacs stuff (thinking about
<a href="https://github.com/kristijanhusak/vim-dadbod-ui">vim-dadbod-ui</a> — what a funky name, btw), some less (try to beat
Vertico + Marginalia + Orderless). Debugging ruby stuff works out of the box.</p>
</div>
<div class="paragraph">
<p>I have made my Neovim as similar to my Doom emacs configuration as possible, so switching is close to seamless. But I
miss a good integrated terminal experience - I am not very happy with how <code>:terminal</code> behaves, and it feels kind of odd
to open a terminal inside an IDE which lives in a terminal. I am trying to find a way to overcome this, but for now,
this is where some of the friction comes from.</p>
</div>
<div class="paragraph">
<p>This is usually when I open <a href="https://zed.dev">Zed</a>.</p>
</div>
<div class="paragraph">
<p>(Well, sometimes, I will start a <a href="https://helix-editor.com">Helix</a> session, get excited by the speed and simplicity, and then get frustrated for
 <a href="https://github.com/helix-editor/helix/discussions/3806">the lack of a plugin system</a> — mainly for git/JJ, running tests and that stuff — read the <a href="https://github.com/helix-editor/helix/blob/master/CHANGELOG.md">11-month-old release notes</a> and
related issue/PR threads, realize this is not going anywhere, and <strong>only then</strong>, open Zed.)</p>
</div>
<div class="paragraph">
<p>Zed is cool. It is like VScode stripped of the bloat and all the Microsoft cringe. It is sexy. Its development is
moving fast. It features AI as a first-class citizen, but does not shove it up to your face without asking you first, and you can ignore it altogether. Zed respects
its users by trying to serve them with a good product, not shove <code>&lt;paste the latest hype&gt;</code> junk down their throat to
get ready for the next VC fund. At least for now.</p>
</div>
<div class="paragraph">
<p>Zed is not a TUI, but <a href="https://zed.dev/blog/videogame">it is damn fast</a>. I get all the snappiness of Neovim, along with most keybindings. I get a rich
plugin ecosystem. But it is still somewhat limited compared to Emacs and Neovim — I am not blaming the Zed team,
though, I am comparing a 4-year-old project to IDE veterans (considering Neovim and its ecosystem is built on the
foundations of Vim).</p>
</div>
<div class="paragraph">
<p>Zed is currently lacking a Telescope-like picker (<a href="https://github.com/zed-industries/zed/pull/46478">but it is coming</a>), a merge tool, and a more capable plugin system to
be able to connect to, and manage your database, for instance, or offer the possibility to manage <a href="https://github.com/jj-vcs/jj">Jujutsu</a> natively (you
can do some of that stuff with workarounds, mainly with tasks). But the main friction comes from the Vim keymap
integration: it is good, but it is not available everywhere…​ like, you cannot do some stuff when in menu mode. So
if you want a mouse-less  experience, it just does not cut it.</p>
</div>
<div class="paragraph">
<p>So I launch Emacs…​ Emacs is where I feel at home, wearing my comfy slippers. I am editing the final draft of this post in Emacs right now, actually.</p>
</div>
<div class="paragraph">
<p>But that’s until I get frustrated again and decide to launch Neovim again…​</p>
</div>
<div class="paragraph">
<p>What is wrong with me? 🙄</p>
</div>
]]></content:encoded></item><item><title>How I Use Mise to Harmonize My Project Commands</title><link>/post/2026-06-15-how-i-use-mise-to-harmonize-my-project-commands/</link><pubDate>Fri, 12 Jun 2026 15:19:51 +0200</pubDate><guid>/post/2026-06-15-how-i-use-mise-to-harmonize-my-project-commands/</guid><description>Mise is a great tool to define and alias commands across all your projects. I use it to avoid having to remember different commands for each project.</description><content:encoded><![CDATA[<div class="paragraph">
<p>Like most developers, I hop from one project to another daily. I switch from Ruby to TypeScript, Terraform or Python. Everything goes fine until I have to <strong>run</strong> something.</p>
</div>
<div class="paragraph">
<p>What does this tool use, already? Is it Yarn? Make? Or framework-native commands? How do I launch a console? Update snapshots when I run the tests? Lint or format the code? I usually roam through the readme, or the Makefile, or whatever makes senses to get to the command I was looking for.</p>
</div>
<div class="paragraph">
<p>What if I had just one command to remember per action? One command that would be valid for every single project I am working with?</p>
</div>
<div class="paragraph">
<p>Mise allows you to define tasks globally, or locally in your <code>mise.local.toml</code>.</p>
</div>
<div class="paragraph">
<p>I have defined a set of basic mise tasks that cover most of my needs in most projects. In my global config, they default to my most common setup/language. For instance, <code>mise run server</code> will alias to <code>bin/rails server</code>, <code>mise run test</code> will alias to <code>bundle exec rspec</code>. This will be fine for most of my rails projects.</p>
</div>
<div class="paragraph">
<p>But some projects I work on use Minitest. So I will create a <code>mise.local.toml</code> in the root directory of that project and change the command to <code>bundle exec test</code>.</p>
</div>
<div class="paragraph">
<p>The front-end application server will also be configured locally to run <code>yarn dev</code> or whatever makes sense to that project.</p>
</div>
<div class="paragraph">
<p>The same goes for linting, formatting, building, or any other task.</p>
</div>
<div class="paragraph">
<p>The <code>mise.local.toml</code> file is globally git-ignored, so it is just my own sauce, and I am not bothering any of my co-workers with my private commands.</p>
</div>
]]></content:encoded></item><item><title>Daily Notes - A Different Approach to Blogging</title><link>/post/daily-notes-a-different-approach-to-blogging/</link><pubDate>Thu, 11 Jun 2026 07:45:23 +0200</pubDate><guid>/post/daily-notes-a-different-approach-to-blogging/</guid><description>I have decided to start a daily notes habit. I will write every day, and share some of my writings here. This post explains my motivations and the rules I set for myself.</description><content:encoded><![CDATA[<div class="paragraph">
<p>I love writing. I am not a very vocal person, and that’s probably out of a fear of saying the right things the wrong
way. Writing gives you a proxy to avoid being misunderstood: you can re-phrase, leave your text to rest for a while,
remove and amend stuff. You can re-think stuff and maybe never ever make it public in the end. This is probably why I
am more comfortable with writing than with speaking.</p>
</div>
<div class="paragraph">
<p>I have recently decided to re-focus on old creative activities I have somehow stopped doing over the years of becoming
a father, a partner, changing career and more. These include music, electronics and writing.</p>
</div>
<div class="paragraph">
<p>I am starting this daily notes habit. I will write every day. Not each session will produce a note here, though. Some
of my writings will go to this blog, but I hope to start another, more personal one quite soon. Some posts also will
be deeper and longer, and require a few day’s work and research.</p>
</div>
<div class="paragraph">
<p>Another promise I made myself: I will NOT use AI. Neither for ideation, proof-reading, or rewording. Yes, the content
I will produce will sometimes be clumsy or unclear, but it will be my content.  It will be <strong>me</strong>. So please forgive me
if my thoughts sometimes seem hard to follow — it is just who I am. I will be learning a lot in the process, and
  hopefully see improvements along the way.</p>
</div>
<div class="paragraph">
<p>I am currently just starting this series, and I hope I can stick to this effort for a long time.</p>
</div>
<div class="paragraph">
<p>I’ll keep you posted 😜 !</p>
</div>
]]></content:encoded></item><item><title>I Am Not a Robot</title><link>/post/i-am-not-a-robot/</link><pubDate>Wed, 03 Jun 2026 12:24:12 +0200</pubDate><guid>/post/i-am-not-a-robot/</guid><description>I have a confession to make: I am not a robot. Should I let my coding style be dictated by the fear of sounding like one? I don’t think so.</description><content:encoded><![CDATA[<div class="paragraph">
<p>I have a confession to make: <em>I am not a robot</em>.</p>
</div>
<div class="paragraph">
<p>But lately, I must admit I felt the urge to make sure I did not read like one. Let me explain:</p>
</div>
<div class="paragraph">
<p>I am the kind of developer who loves clean code and knows the <a href="https://www.poodr.com/">POODR book</a> by heart in 7 languages (I don’t, but you get the idea). I always thoroughly document my code with Yard. I stick to TDD as much as I can. I try to write extensive commit messages and PR descriptions.</p>
</div>
<div class="paragraph">
<p>On the other hand, I love to try new, shiny things. I change patterns when I feel like it (my latest <em>personal coding-style hype</em> was inlining <code>private</code> keywords in ruby method definitions). My tests never look exactly the same. I am, in some ways, inconsistent.</p>
</div>
<div class="paragraph">
<p>When I code, I often go the extra mile and refactor stuff along the way, which makes my PRs a little fatter and less laser-focused than they should.</p>
</div>
<div class="paragraph">
<p>But lately, as I was writing a very detailed commit message, with lots of bullet points and em-dashes to make it extra-clear what my intent was, it occurred to me that all of these made me sound like a LLM. In fact, the whole thing I was about to ship could appear as vibe-coded — it was not.</p>
</div>
<div class="paragraph">
<p>What will the reviewers think of my work? What will they think of me? A vibe coder? No way.</p>
</div>
<div class="paragraph">
<p>Let me get this straight: I do use AI. But it is my pair programmer, my assistant, my rubber duck, my intern — with one caveat: I have to make my own coffee.</p>
</div>
<div class="paragraph">
<p>I am in charge. I code. I write tests.</p>
</div>
<div class="paragraph">
<p>So, for a moment, as I was proof-reading that commit message, I was tempted to add a few inconsistencies, remove the em-dashes and keep a few typos…​</p>
</div>
<div class="paragraph">
<p>I didn’t.</p>
</div>
<div class="paragraph">
<p>Screw it, I am not a robot.</p>
</div>
]]></content:encoded></item><item><title>Directory-based git config rules</title><link>/post/directory-based-git-config-rules/</link><pubDate>Mon, 01 Jun 2026 16:22:00 +0200</pubDate><guid>/post/directory-based-git-config-rules/</guid><description>I have found a way to alter my git config based on the directory I am working from. Pretty simple, yet very handy.</description><content:encoded><![CDATA[<div class="paragraph">
<p>Like most devs, I use Git both for personal and work stuff, but I need to use distinct user details in each case to sign-off my commits. I have a global <code>.gitconfig</code> that define my personal email as <code>user.email</code>, but whenever I need to pull a new work project, I have to remember to set it to a different value locally. Which I keep forgetting to do.</p>
</div>
<div class="paragraph">
<p>So I have found a way to alter my git config based on the directory I am working from. All you need to do is add these lines to your <code>~/.gitconfig</code>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="toml"><span class="k">[</span><span class="n">user</span><span class="k">]</span>
  <span class="n">name</span> <span class="o">=</span><span class="w"> </span><span class="err">My Name
  email = personal@email.com

# Use work email for repositories under ~/dev/work
</span><span class="p">[</span><span class="err">includeIf </span><span class="s">&#34;gitdir:~/dev/work/&#34;</span><span class="p">]</span>
  <span class="n">path</span> <span class="o">=</span><span class="w"> </span><span class="err">~/.gitconfig-work</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>And edit <code>.gitconfig-work</code>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="toml"><span class="k">[</span><span class="n">user</span><span class="k">]</span>
  <span class="n">email</span> <span class="o">=</span><span class="w"> </span><span class="err">work-email@mycompany.com</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>I think this also applies to other git config values, so feel free to experiment!</p>
</div>
]]></content:encoded></item><item><title>This Blog Does Not Track You (But You Are Invited to Track It)</title><link>/post/this-blog-does-not-track-you-but-you-are-invited-to-track-it/</link><pubDate>Fri, 22 May 2026 14:48:43 +0200</pubDate><guid>/post/this-blog-does-not-track-you-but-you-are-invited-to-track-it/</guid><description>There are no cookies, no counters, no analytics attached to this blog. I just write for myself and I don’t care about the audience. But you are invited to track it if you want to!</description><content:encoded><![CDATA[<div class="paragraph">
<p>This is just a quick note to tell you that this blog does not track you at all. There are no cookies no counters analytics attached to this webpage or to any other on this website.</p>
</div>
<div class="paragraph">
<p>I used to have this. I used to track every access to all of my posts on this blog. And sometimes often it was frustrating to see that some posts didn’t get the attention I thought they deserved — take that, hubris.</p>
</div>
<div class="paragraph">
<p>I used to write in order to reach an audience or increase my visibility on the internet for professional purposes or gain recognition in the Ruby community.</p>
</div>
<div class="paragraph">
<p>Now I don’t care.</p>
</div>
<div class="paragraph">
<p>I just write because I enjoy it. These posts are merely my thoughts, laid out in words. And…​ I don’t track my thoughts. I don’t copyright them or monetize them. Why would I do it here? This is my personal tech journal that just happens to be publicly available.</p>
</div>
<div class="paragraph">
<p>You may read this blog. You may not. You may subscribe to my <a href="https://moskitohero.com/index.xml#feed">RSS feed</a> or follow me on social media. You may like what you read, some, or none of it. You may find my thoughts bland or outrageous. But you may not change them. I will not adapt to an audience based on what &#34;works&#34;.</p>
</div>
<div class="paragraph">
<p>That’s also why there are no comments here either.</p>
</div>
<div class="paragraph">
<p>If you really want to start a conversation and interact with me, you can find me on <a href="https://ruby.social/@MoskitoHero">Mastodon</a> or <a href="https://bsky.app/profile/moskitohero.bsky.social">Bluesky</a> (or even <a href="https://www.linkedin.com/in/cedric-delalande/">LinkedIn</a> — why not).</p>
</div>
<div class="paragraph">
<p>And you know what? Writing this way is freeing. I have absolutely no pressure, except for my own desire to write and make it a sane habit.</p>
</div>
<div class="paragraph">
<p>I obviously hope people enjoy reading this. But I firmly believe that my posts will be all the more interesting if I am not writing for anyone, except for myself.</p>
</div>
]]></content:encoded></item><item><title>Make Your Vcs Fly With Jujutsu</title><link>/post/make-your-vcs-fly-with-jujutsu/</link><pubDate>Thu, 21 May 2026 08:23:11 +0200</pubDate><guid>/post/make-your-vcs-fly-with-jujutsu/</guid><description>I am always attracted by new tools that promise to make my life easier. I recently started using Jujutsu, a version control system that makes your git workflow much more flexible.</description><content:encoded><![CDATA[<div class="paragraph">
<p>I like to try things out. I like new, shiny, alternative stuff that is not hype yet. I created my first Twitter account when you could see the world timeline of tweets refresh in real time on the home page. I also like new ideas that build upon great systems in order to improve them or take them a few steps further. That’s why I use <a href="https://zellij.dev">Zellij</a>, not Tmux. That’s why I use <a href="https://www.nushell.sh/">Nushell</a>, not bash, or zsh, as my main terminal shell. And that’s why I started using <a href="https://www.jj-vcs.dev">Jujutsu</a> to version control my code.</p>
</div>
<div class="paragraph">
<p>Jujutsu is a version control system UX that builds upon multiple VCS backends (Git, Mercurial, Breezy…​). Jujutsu (or JJ) provides a user-facing abstraction that makes your git workflow much more flexible.</p>
</div>
<div class="paragraph">
<p>For instance, there is no such things as branches. The closest equivalent is bookmarks. But bookmarks are more akin to mutable git tags. Git commits belong to a branch, and move when the branch gets merged into another. JJ puts this the other way round: bookmarks behave the way you’d expect a bookmark to behave: they can be moved around revsets (the jj equivalent of git commits). This alone opens a wide range of possibilities.</p>
</div>
<div class="paragraph">
<p>One of the key difference with Git is that you do not have a staging area. Instead, you tend to use revsets as throwaway commits to keep track of your work in progress. You can then squash them, or move them around, or abandon them. You can come back to an earlier revision and edit it! It’s as simple as calling <code>jj edit nf</code> (<code>nf</code> being the shortcode of the revision you wish to edit) and go modify your code. No commit needed. The revision is edited as is.</p>
</div>
<div class="paragraph">
<p>No stashes either. I have a list of bookmarks with a <code>stash</code> prefix that I use as stashes, but they are just revsets that I can move around and manipulate as I wish.</p>
</div>
<div class="paragraph">
<p>Other killer feature: conflicts are not blockers. You can have a conflict in your code, but you can still commit and move on, dealing with the conflict later on. Especially useful when you are working on a feature branch and want to keep up with the main branch. You can just merge the main branch into your feature branch, and deal with the conflicts later on, when you have more time to focus on them.</p>
</div>
<div class="paragraph">
<p>The icing on the cake is the undo/redo feature. Yes, you read that right. JJ provides an operation log (<code>jj op log</code>) that tracks all your operations, including commits, merges, rebases, splits, etc. Calling <code>jj undo</code> will undo the last operation.</p>
</div>
<div class="paragraph">
<p>JJ lives on top / besides of your git repo. All you have to do after you install JJ is cd into your repository and run <code>jj git init</code>. Voilà, you can now use JJ with your git backend.</p>
</div>
<div class="paragraph">
<p>It provides you with home-made, yet efficient diff and merge tool, but you can also use your own (Meld, Neovim, Ediff, you name it). It is designed to be very user-friendly from the command line — much more than git — but a few TUI clients and IDE integrations also exist. More on that in a later post.</p>
</div>
<div class="paragraph">
<p>I started using JJ a few weeks ago, and I never looked back. I really feels it makes my VCS workflow much less of a burden.</p>
</div>
<div class="paragraph">
<p>I will probably post more about the way I use it day-to-day, along with configuration tips and tools. Stay tuned!</p>
</div>
]]></content:encoded></item><item><title>Disconnected: One Day Working Without the Internet</title><link>/post/2026-05-16-disconnected-one-day-working-without-the-internet/</link><pubDate>Sat, 16 May 2026 06:09:59 +0200</pubDate><guid>/post/2026-05-16-disconnected-one-day-working-without-the-internet/</guid><description>What does it feel like to work without the internet in 2026? Circumstances got me unplugged myself for a day, leaving me with a flaky 5G connection to rely on.</description><content:encoded><![CDATA[<div class="paragraph">
<p>When I got up this morning, I quickly realized that my landline was out of service. Called my ISP, and apparently , it is due to thunderstorms in my area last night.</p>
</div>
<div class="paragraph">
<p>Pretty bad news. As a full-remote engineer, this means I will have to rely on the flaky 5G network I get round my house to work and communicate with co-workers.</p>
</div>
<div class="paragraph">
<p>I realize that I will have to write code in a completely gated environment, which, two days after writing <a href="https://moskitohero.com/post/2026-05-12-opening-the-mind-of-a-thinking-agent/">a post about unplugging AI and reclaiming my thinking</a>, means that I will have to code without the help of AI. This is a good test, a kind of reality check. What does it feel to code like it’s 2010 again?</p>
</div>
<div class="paragraph">
<p>And what a good idea for a blog post! I am going to write down my thoughts here along the way.</p>
</div>
<div class="sect2">
<h3 id="827-am"><a class="anchor" href="#827-am"></a>8:27 am</h3>
<div class="paragraph">
<p>Re-opening my IDE where I left it yesterday only to realize that the feature I was working on is a front-end task in TypeScript / React 🤡. I am Ruby backend developer, but I get to do these kind of features for the admin from time to time, and I don’t mind. I can code in TypeScript, but this is going to be a little more challenging than I thought — there will be no agent at hand to guide me along the way.</p>
</div>
<div class="paragraph">
<p>Let’s take a deep breath and dive in.</p>
</div>
</div>
<div class="sect2">
<h3 id="908-am"><a class="anchor" href="#908-am"></a>9:08 am</h3>
<div class="paragraph">
<p>It’s quiet in here. Let’s put some music on…​</p>
</div>
<div class="paragraph">
<p>😬</p>
</div>
<div class="paragraph">
<p>Oh well, all I have is an Apple Music subscription! Where is my music collection? I guess that’s something I must address sooner or later. This is not work-related, but it is telling of our dependency to the internet.</p>
</div>
</div>
<div class="sect2">
<h3 id="942-am"><a class="anchor" href="#942-am"></a>9:42 am</h3>
<div class="paragraph">
<p>Time for a break. I am feeling quite productive so far. I do lose a little time, though, due to the absence of code completion &#34;on steroids&#34;. I have to type most of my code the old-school way, tabbing through variable completions. It’s fine.</p>
</div>
<div class="paragraph">
<p>My co-workers should now be up and working. I tell them about my network issues and re-activate notifications on my phone Slack app.</p>
</div>
</div>
<div class="sect2">
<h3 id="1107am"><a class="anchor" href="#1107am"></a>11:07am</h3>
<div class="paragraph">
<p>I need to check the JIRA ticket I am working on to get some exact wording for a string. I don’t have the JIRA app installed on my phone, so I share my phone’s connection to the computer.</p>
</div>
<div class="paragraph">
<p>This is sluggish. Like 1995/33k-modem-sluggish. And JIRA is not the lightest UI around.</p>
</div>
<div class="paragraph">
<p>While the UI loads, I remember that I have the JIRA extension set up in Raycast. This will mean simple, faster API calls. It works and responds almost instantly. I can read the specs and get on with my work.</p>
</div>
<div class="paragraph">
<p>But it leaves me thinking: how on earth did we end up with such bloated web apps? There is such a contrast between the delay I experienced fetching data through the API (a few kilobytes in a few milliseconds) and the lagging UI that loads up megabytes of javascript just to have it displayed on a page. There is something broken here. We all know it. But actually experiencing it sheds a different light.</p>
</div>
<div class="paragraph">
<p>The irony of it all is that I am currently working on a React project used to render the admin backend at my company. The typical example of what React should not be used for — and yes, trust me, it si terribly slow to navigate.</p>
</div>
</div>
<div class="sect2">
<h3 id="1144-am"><a class="anchor" href="#1144-am"></a>11:44 am</h3>
<div class="paragraph">
<p>I had to do the daily meeting with my squad in the garden.</p>
</div>
<div class="paragraph">
<p>Currently looking for how to do string interpolation in TypeScript…​ Good old Stack Overflow comes to the rescue on my phone. I heard they are going through hard times with the rise of vibe coding. I hope we don’t get to the point where they have to shut down. They are the Wikipedia of software engineering. They should be part of the commons and kept as a public utility service. What will happen when you really have to pay for AI (I mean the <em>real</em>  price, not the huge discount we get these days)?</p>
</div>
<div class="paragraph">
<p>Still no internet. One neighbor just told me that it is going to last a few days. My ISP proposes to lend me a 4G home router in the mean time.</p>
</div>
</div>
<div class="sect2">
<h3 id="205-pm"><a class="anchor" href="#205-pm"></a>2:05 pm</h3>
<div class="paragraph">
<p>I need to restart both front and backend servers, and this hurts. Front-end keeps repeating <code>There appears to be trouble with your network connection. Retrying…​</code> . Backend is stuck…​</p>
</div>
<div class="paragraph">
<p>I decide to share my lousy 5G connection. After a few minutes, my phone picks up a signal, and servers boot. At last!</p>
</div>
<div class="paragraph">
<p>Coding locally should be possible without the need for an internet connection. If you server calls external APIs as it boots, it should be fixed — what happens if they are down? Will file a ticket for this.</p>
</div>
</div>
<div class="sect2">
<h3 id="223-pm"><a class="anchor" href="#223-pm"></a>2:23 pm</h3>
<div class="paragraph">
<p>I have a question on the way TypeScript handles <code>undefined</code> and booleans, and whether I can do some kind of type casting — I know I can, I just can’t remember how.</p>
</div>
<div class="paragraph">
<p>My first reaction is to fire up a search engine on Vivaldi. Ah yes, no internet. I get up, walk to the garden and find the answer to this. Not the shortest feedback loop. It would have been much worse if I had to go look up an obscure error message and figure out what it means.</p>
</div>
</div>
<div class="sect2">
<h3 id="248-pm"><a class="anchor" href="#248-pm"></a>2:48 pm</h3>
<div class="paragraph">
<p>I am quite satisfied with my work now. Let’s review it.</p>
</div>
<div class="paragraph">
<p>This is usually when Claude walks in. I guess I will have to review it closely myself before I create the PR. This is hard, actually. I am not fluent in TypeScript, so I don’t have much to say about the stuff I have written. I just remove some unneeded code. Fingers crossed!</p>
</div>
</div>
<div class="sect2">
<h3 id="306-pm"><a class="anchor" href="#306-pm"></a>3:06 pm</h3>
<div class="paragraph">
<p>Time to push. It’s connection-sharing time again. This is a little tricky, and I am moving my phone round the house, desperately trying to find a proper signal. Once I get something, I fetch latest main, but by the time I rebase, the signal is gone, so I have to find a better spot.</p>
</div>
<div class="paragraph">
<p>After a few minutes, the signal stabilizes and I can do some online stuff: push, create the PR, notify the reviewers…​ Let’s hope Github doesn’t decide it’s time to have an outage…​</p>
</div>
</div>
<div class="sect2">
<h3 id="427pm"><a class="anchor" href="#427pm"></a>4:27pm</h3>
<div class="paragraph">
<p>Yay! It’s the weekend!</p>
</div>
<div class="paragraph">
<p>Overall, this was an interesting experience. It was satisfying to find out that I can still code by myself, like a grown-up developer. It was also quieter, with less interruptions to deal with, since Slack notifications arrived when my phone caught a signal, so it was more a question of me reaching to my phone than the other way round.</p>
</div>
<div class="paragraph">
<p>I think I am going to reproduce this regularly. Switch off the wi-fi and work with no strings attached. But I need to regain control of my music library first!</p>
</div>
<div class="paragraph">
<p>And guess what? As I am writing these words, I just noticed that the internet is back on…​ True story.</p>
</div>
</div>
]]></content:encoded></item><item><title>Opening the Mind of a Thinking Agent</title><link>/post/2026-05-12-opening-the-mind-of-a-thinking-agent/</link><pubDate>Tue, 12 May 2026 22:10:00 +0000</pubDate><guid>/post/2026-05-12-opening-the-mind-of-a-thinking-agent/</guid><description>Reflecting on my experience of using AI agents for coding, I start wondering what is really hiding behind the concept of a &amp;#34;thinking agent&amp;#34;. Time to take a look inside its brain…​</description><content:encoded><![CDATA[<div class="paragraph">
<p>My brain is a dumpster tonight.</p>
</div>
<div class="paragraph">
<p>I kind of felt lazy all day, so I decided to rely on agents to leverage most of the work I had to do. The result is that I now feel totally depressed and tired. Everything was wrong with that day’s work.</p>
</div>
<div class="paragraph">
<p>It was tempting, at first. I thought I would have AI do the boring bits, type less on my keyboard and become an orchestrator. I thought it would give me more control over my priorities and help me dash through the day like a warrior, like a knight proudly riding his horse.</p>
</div>
<div class="paragraph">
<p>Now it’s 6 p.m. It feels like the horse has been trampling on my back all afternoon and is now chewing on my hair.</p>
</div>
<div class="paragraph">
<p>What did I get from today’s coding session? I remember giving instructions to the agent. I remember waiting for it to reply. I remember being unsatisfied with what it proposed as a solution. I remember guiding him step by step through the process. I remember looking at the screen in disbelief as it took five minutes to move a piece of code from one file to another.</p>
</div>
<div class="paragraph">
<p>I could have done it in seconds. Still, I was kind of relieved not to do the boring chores.</p>
</div>
<div class="paragraph">
<p>Looking back, I feel that what really happened is that I switched off my brain and surrendered it to the Machine. Now I feel ashamed.</p>
</div>
<div class="paragraph">
<p>All that time the machine was thinking. I wasn’t.</p>
</div>
<div class="sect2">
<h3 id="thinking"><a class="anchor" href="#thinking"></a><em>Thinking…​</em></h3>
<div class="paragraph">
<p>That word intrigued me. So I clicked on it. A toggle opened. And man, was that ugly…​</p>
</div>
<div class="paragraph">
<p>What AI companies advertise as thinking is actually a computer program mumbling for ages, balancing ideas and their opposites, trying to figure out what exactly I meant, taking U-turns, battling against itself on details so insignificant it would drive anyone with a sane mind totally nuts. This is not thinking. This is vomiting words by the gallon. This is a brute-force parody of a french political debate.</p>
</div>
<div class="paragraph">
<p>My experience of thinking is not filling up pages and pages of words based on the likelihood of their occuring in a single sequence, and then taking these pages of writing to write yet another round of pages of nonsense until I decide I am confident enough to tell you I have finished thinking.</p>
</div>
<div class="paragraph">
<p>My experience of thinking actually implies growing up as a person and as a professional, backed by an intimate network of neurons I have patiently connected throughout the years by the means of experience, reading, learning, meeting, confronting ideas.</p>
</div>
<div class="paragraph">
<p>My experience of thinking is tied to the dose of dopamine I get when, Eureka! I finally found the solution. I didn’t get my fix today. I gave it up to Claude, who took five long minutes of water-heating, climate-warming and deforesting to spit out an unconvincing solution while I looked bluntly at my screen like a Half-Life zombie crying out for brain.</p>
</div>
<div class="literalblock">
<div class="content">
<pre>🧟‍♂️🧟‍♂️🧟‍♂️ Braaaaaiiiin... 🧠🧠🧠</pre>
</div>
</div>
<div class="paragraph">
<p>Luckily enough, I got woken up by a message telling me I had reached my daily quota. That’s when I realized the pain and frustration I had been inflicting myself.</p>
</div>
<div class="paragraph">
<p>Thinking is a pleasure. I will not let a machine with no feelings take it away from me.</p>
</div>
</div>
<div class="sect2">
<h3 id="lets-wrap-it-up"><a class="anchor" href="#lets-wrap-it-up"></a>Let’s wrap it up.</h3>
<div class="paragraph">
<p>I am not anti-AI. That would be stupid. Like being anti-electricity, or anti-dishwasher, or anti-railway. I just want to keep my agent saddled-up tight underneath me like a faithful horse I can control. I want to <strong>use</strong> AI, and never let it be the other way round.</p>
</div>
<div class="paragraph">
<p>I feel it is time to review my AI deontology. This is the plan:</p>
</div>
<hr/>
<div class="ulist">
<ul>
<li>
<p>Remove uneeded CLI agents I have installed just &#34;to try it out&#34;.</p>
</li>
<li>
<p>Remove IDE plugins, extensions, apart from code completion stuff. That is often useful.</p>
</li>
<li>
<p>In Zed, check &#34;disable AI&#34;</p>
</li>
<li>
<p>Keep Claude Code (company subscription) installed — Claude  desktop was already uninstalled a long time ago — and stick to these rules:</p>
<div class="ulist">
<ul>
<li>
<p>Only reviews and ideation</p>
</li>
<li>
<p>No thinking models</p>
</li>
<li>
<p>No write access to anything whatsoever. Yes, even elisp.</p>
</li>
</ul>
</div>
</li>
</ul>
</div>
<hr/>
<div class="paragraph">
<p>And for the rest, I will stick to good old Organic Intelligence.</p>
</div>
</div>
]]></content:encoded></item><item><title>Zed Turtle Extension</title><link>/post/2025-05-19-zed-turtle-extension/</link><pubDate>Mon, 19 May 2025 00:00:00 +0000</pubDate><guid>/post/2025-05-19-zed-turtle-extension/</guid><description>I have just released the first version of a Zed extension for the Turtle format.</description><content:encoded><![CDATA[<div class="paragraph">
<p>I have just released the first version of <a href="https://github.com/MoskitoHero/zed-turtle">a Zed extension for the Turtle format</a>.</p>
</div>
<div class="sect1">
<h2 id="zed"><a class="anchor" href="#zed"></a>Zed?</h2>
<div class="sectionbody">
<div class="paragraph">
<p><a href="https://zed.dev">Zed</a> is a very cool text editor / IDE by the creators of Atom and Tree-Sitter. It is written in Rust and it caught my interest because it is very very lean, it has Vim, JetBrains, VSCode, Helix and Emacs keybindings and a growing extension system. It has multiple advantages (at least in my opinion) over the competition, and I feel it builds upon other IDEs’ strengths while being very pleasant to use. It has pair-programming and remote development baked-in from day one.
Most importantly, it is not owned by a multi-billionnaire-led, fascist-supporting company that will eventually sunset or paywall the product once the whole world is addicted to it. And yes, it features AI, MCP and agentic editing if that’s your thing, but that’s something you can turn off, which I did.
Zed is currently driving me away from Emacs (and that means <em>something</em> if you ask me). You really should give it a try. <a href="https://zed.dev/download">Stop reading and download it</a>.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="turtle"><a class="anchor" href="#turtle"></a>Turtle?</h2>
<div class="sectionbody">
<div class="paragraph">
<p>That’s the RDF file format I use extensively at work. It renders RDF graph triplets in a readable format, making it easier to manage than plain RDF/XML or JSON-LD other semantic web file formats.
This is an sample Turtle file:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="turtle"><span class="kd">@prefix</span><span class="w"> </span><span class="nn">rdf:</span><span class="w"> </span><span class="nl">&lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&gt;</span><span class="w"> </span><span class="p">.</span><span class="w">
</span><span class="kd">@prefix</span><span class="w"> </span><span class="nn">dc:</span><span class="w"> </span><span class="nl">&lt;http://purl.org/dc/elements/1.1/&gt;</span><span class="w"> </span><span class="p">.</span><span class="w">
</span><span class="kd">@prefix</span><span class="w"> </span><span class="nn">ex:</span><span class="w"> </span><span class="nl">&lt;http://example.org/stuff/1.0/&gt;</span><span class="w"> </span><span class="p">.</span><span class="w">

</span><span class="s">&#39;&#39;</span><span class="n">&#39;

</span><span class="nl">&lt;http://www.w3.org/TR/rdf-syntax-grammar&gt;</span><span class="w">
  </span><span class="nn">dc:</span><span class="n">title</span><span class="w"> </span><span class="s">&#34;RDF/XML Syntax Specification (Revised)&#34;</span><span class="w"> </span><span class="p">;</span><span class="w">
  </span><span class="nn">ex:</span><span class="n">editor</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="nn">ex:</span><span class="n">fullname</span><span class="w"> </span><span class="s">&#34;Dave Beckett&#34;</span><span class="p">;</span><span class="w">
    </span><span class="nn">ex:</span><span class="n">homePage</span><span class="w"> </span><span class="nl">&lt;http://purl.org/net/dajobe/&gt;</span><span class="w">
  </span><span class="p">]</span><span class="w"> </span><span class="p">.</span></code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="extension"><a class="anchor" href="#extension"></a>Extension?</h2>
<div class="sectionbody">
<div class="paragraph">
<p>So why a RDF extension?
As I was editing .ttl files for work in Zed, I started looking for an extension to highlight my code and navigate through it, but didn’t find one. So I decided to create my own!
I looked at the Zed developer documentation, took a little inspiration from existing extensions and started writing my very first Rust lines of code — which was another motivation for starting that endeavor.
All I had to do is gather resources scattered here and there on the internet:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>A <a href="https://github.com/GordianDziwis/tree-sitter-turtle">ready-made tree-sitter project</a> for Turtle</p>
</li>
<li>
<p>A language server available <a href="https://github.com/stardog-union/stardog-language-servers">here</a></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>…​and write a few lines of code to turn those into usable Zed features.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="the-features"><a class="anchor" href="#the-features"></a>The features</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Here is what the extension does for now:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Code highlighting</p>
</li>
<li>
<p>Symbol navigation</p>
</li>
<li>
<p>LSP — the server is automatically downloaded when you edit your first Turtle document.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>What I plan on adding in a near future is:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Formatting</p>
</li>
<li>
<p>SHACL validation</p>
</li>
<li>
<p>A better LSP with go to definition / implementation (and maybe formatting as part of the LSP)</p>
</li>
</ul>
</div>
<div class="paragraph">
<p><span class="image"><img src="/assets/img/zed-turtle.png" alt="The Zed Turtle extension featuring the symbol navigation"/></span>
<em>The Zed Turtle extension featuring the symbol navigation</em></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="what-you-can-do"><a class="anchor" href="#what-you-can-do"></a>What you can do</h2>
<div class="sectionbody">
<div class="paragraph">
<p>I hope the plugin serves both the Zed and the Semantic Web communities.
If you are interested in this extension, do not hesitate to <a href="https://zed.dev/extensions?query=turtle">give it a try</a>, <a href="https://github.com/MoskitoHero/zed-turtle/issues">report issues or add feature requests</a>, or even <a href="https://github.com/MoskitoHero/zed-turtle/pulls">give me a hand</a> to improve it!</p>
</div>
</div>
</div>
]]></content:encoded></item><item><title>An introduction to Barley, the fast model serializer</title><link>/post/2025-05-06-an-introduction-to-barley-the-fast-model-serializer/</link><pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate><guid>/post/2025-05-06-an-introduction-to-barley-the-fast-model-serializer/</guid><description>How I built and open-sourced Barley, a fast and simple model serializer gem for Rails, born from real-world performance needs and benchmarked against the best in the Ruby ecosystem.</description><content:encoded><![CDATA[<div class="sect1">
<h2 id="the-story"><a class="anchor" href="#the-story"></a>The story</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The company I used to work for had an ageing API in v1 that was very poorly designed and slow in its implementation. It served 4 frontend platforms (a website, two mobile apps and an administration tool). Being in a startup environment, our team had to move fast, but that was at the expense of performance and maintainability. This was the situation when I arrived there.
Returning JSON objects was very cumbersome. It used a custom method, available in each model that built a nested Hash, based on the attributes you gave it. That turned out to be a mess with poor performance and concerning security implications. It was difficult to maintain, with odd variable naming and behavior, so we set out to get rid of it somehow and moved to a kind of serializer DSL that was gradually introduced to replace the old mess.
Later, we decided to tighten up our API with a new version to get rid of the legacy. A few requirements quickly emerged, among which:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>API V2 should be easy to maintain, with clear naming conventions and namespacing</p>
</li>
<li>
<p>Since it is an internal API, it should be tightly coupled to the intended use and platform</p>
</li>
<li>
<p>API V2 should use some sort of view DSL to generate the JSON payloads</p>
</li>
<li>
<p>API V2 should be as predictable as possible in terms of typing and formatting.</p>
</li>
<li>
<p>API V2 should be super fast by default. It should be the last thing we need to optimize.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>So I looked at the serializer DSL that we had designed for API v1.
The first thing I did was to benchmark it against other solutions. So I stripped it out of legacy / no longer needed features, moved it to a local gem, and started playing with a few benchmarks.
And it was <em>very fast</em> indeed ! This is the result of a <a href="https://github.com/MoskitoHero/active_model_serializers/tree/benchmarks">benchmark</a> I did back then:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell">Warming up <span class="nt">--------------------------------------</span>
<span class="c"># Omitted for conciseness</span>
Calculating <span class="nt">-------------------------------------</span>
ams                    67.770  <span class="o">(</span>± 1.9%<span class="o">)</span> i/s -    657.000  <span class="k">in  </span>10.042270s
jsonapi-rb            157.239  <span class="o">(</span>± 2.3%<span class="o">)</span> i/s -      1.521k <span class="k">in  </span>10.010257s
barley                 96.909  <span class="o">(</span>± 2.4%<span class="o">)</span> i/s -    945.000  <span class="k">in  </span>10.036316s
barley-cache            4.589k <span class="o">(</span>± 2.8%<span class="o">)</span> i/s -     44.620k <span class="k">in  </span>10.010004s
ams          eager     68.237  <span class="o">(</span>± 6.2%<span class="o">)</span> i/s -    592.000  <span class="k">in  </span>10.087602s
jsonapi-rb   eager    289.733  <span class="o">(</span>± 3.7%<span class="o">)</span> i/s -      2.805k <span class="k">in  </span>10.033912s
barley       eager    406.732  <span class="o">(</span>± 3.2%<span class="o">)</span> i/s -      3.922k <span class="k">in  </span>10.020076s
barley-cache eager    639.935  <span class="o">(</span>± 2.4%<span class="o">)</span> i/s -      6.300k <span class="k">in  </span>10.053710s
with 95.0% confidence

Comparison:
barley-cache      :     4589.3 i/s
barley-cache eager:      639.9 i/s - 7.17x  <span class="o">(</span>± 0.27<span class="o">)</span> slower
barley       eager:      406.7 i/s - 11.29x  <span class="o">(</span>± 0.49<span class="o">)</span> slower
jsonapi-rb   eager:      289.7 i/s - 15.83x  <span class="o">(</span>± 0.74<span class="o">)</span> slower
jsonapi-rb        :      157.2 i/s - 29.17x  <span class="o">(</span>± 1.08<span class="o">)</span> slower
barley            :       96.9 i/s - 47.37x  <span class="o">(</span>± 1.75<span class="o">)</span> slower
ams          eager:       68.2 i/s - 67.25x  <span class="o">(</span>± 4.69<span class="o">)</span> slower
ams               :       67.8 i/s - 67.72x  <span class="o">(</span>± 2.31<span class="o">)</span> slower
with 95.0% confidence

Calculating <span class="nt">-------------------------------------</span>
<span class="c"># Omitted for conciseness</span>

Comparison:
barley-cache      :      46090 allocated
barley-cache eager:     216790 allocated - 4.70x more
barley       eager:     354326 allocated - 7.69x more
jsonapi-rb   eager:     694430 allocated - 15.07x more
jsonapi-rb        :     926082 allocated - 20.09x more
ams          eager:    1068038 allocated - 23.17x more
barley            :    1102354 allocated - 23.92x more
ams               :    1299674 allocated - 28.20x more</code></pre>
</div>
</div>
<div class="paragraph">
<p>Which is quite nice 😎.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="sharing-is-caring"><a class="anchor" href="#sharing-is-caring"></a>Sharing is caring</h2>
<div class="sectionbody">
<div class="paragraph">
<p>So I made a gem out of our serializer and <a href="https://github.com/MoskitoHero/barley">published it on Github</a>. It is named Barley.
I have recently made the gem even faster (from v0.9.0 on) and benchmarked it against <a href="https://github.com/okuramasafumi/alba">Alba</a>, <a href="https://github.com/procore-oss/blueprinter">Blueprinter</a>, <a href="https://github.com/malomalo/turbostreamer">Turbostreamer</a>, <a href="https://github.com/distil/jserializer">Jserializer</a>, <a href="https://github.com/estepnv/fast_serializer">FastSerializer</a>, <a href="https://github.com/trailblazer/representable">Representable</a>, <a href="https://github.com/vasilakisfil/SimpleAMS">SimpleAMS</a>, <a href="https://github.com/rails-api/active_model_serializers">AMS</a>, <a href="https://github.com/rails/jbuilder">Rails JBuilder</a> and <a href="https://github.com/yosiat/panko_serializer">Panko</a>.
Here are the results of these new benchmarks:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell">ruby 3.4.3 <span class="o">(</span>2025-04-14 revision d0b7e5b6a0<span class="o">)</span> +YJIT +PRISM <span class="o">[</span>x86_64-linux]
Warming up <span class="nt">--------------------------------------</span>
<span class="c"># Omitted for conciseness</span>

Calculating <span class="nt">-------------------------------------</span>
                alba    401.119 <span class="o">(</span>± 1.7%<span class="o">)</span> i/s    <span class="o">(</span>2.49 ms/i<span class="o">)</span> -      2.016k <span class="k">in   </span>5.027350s
alba_with_transformation
                        256.123 <span class="o">(</span>± 1.2%<span class="o">)</span> i/s    <span class="o">(</span>3.90 ms/i<span class="o">)</span> -      1.300k <span class="k">in   </span>5.076497s
         alba_inline     22.749 <span class="o">(</span>± 8.8%<span class="o">)</span> i/s   <span class="o">(</span>43.96 ms/i<span class="o">)</span> -    114.000 <span class="k">in   </span>5.049973s
                 ams     16.923 <span class="o">(</span>± 5.9%<span class="o">)</span> i/s   <span class="o">(</span>59.09 ms/i<span class="o">)</span> -     85.000 <span class="k">in   </span>5.029061s
              barley    427.045 <span class="o">(</span>± 2.6%<span class="o">)</span> i/s    <span class="o">(</span>2.34 ms/i<span class="o">)</span> -      2.162k <span class="k">in   </span>5.066130s
        barley_cache    378.285 <span class="o">(</span>± 2.4%<span class="o">)</span> i/s    <span class="o">(</span>2.64 ms/i<span class="o">)</span> -      1.927k <span class="k">in   </span>5.097462s
         blueprinter    120.704 <span class="o">(</span>± 1.7%<span class="o">)</span> i/s    <span class="o">(</span>8.28 ms/i<span class="o">)</span> -    612.000 <span class="k">in   </span>5.071371s
     fast_serializer    142.347 <span class="o">(</span>± 1.4%<span class="o">)</span> i/s    <span class="o">(</span>7.03 ms/i<span class="o">)</span> -    720.000 <span class="k">in   </span>5.058924s
         jserializer    252.455 <span class="o">(</span>± 1.6%<span class="o">)</span> i/s    <span class="o">(</span>3.96 ms/i<span class="o">)</span> -      1.274k <span class="k">in   </span>5.047584s
               panko    510.262 <span class="o">(</span>± 4.7%<span class="o">)</span> i/s    <span class="o">(</span>1.96 ms/i<span class="o">)</span> -      2.585k <span class="k">in   </span>5.077381s
               rails    107.589 <span class="o">(</span>± 7.4%<span class="o">)</span> i/s    <span class="o">(</span>9.29 ms/i<span class="o">)</span> -    546.000 <span class="k">in   </span>5.104715s
       representable     50.264 <span class="o">(</span>± 6.0%<span class="o">)</span> i/s   <span class="o">(</span>19.89 ms/i<span class="o">)</span> -    252.000 <span class="k">in   </span>5.027987s
          simple_ams     36.584 <span class="o">(</span>± 5.5%<span class="o">)</span> i/s   <span class="o">(</span>27.33 ms/i<span class="o">)</span> -    184.000 <span class="k">in   </span>5.054347s
       turbostreamer    351.551 <span class="o">(</span>± 1.7%<span class="o">)</span> i/s    <span class="o">(</span>2.84 ms/i<span class="o">)</span> -      1.776k <span class="k">in   </span>5.053401s

Comparison:
               panko:      510.3 i/s
              barley:      427.0 i/s - 1.19x  slower
                alba:      401.1 i/s - 1.27x  slower
        barley_cache:      378.3 i/s - 1.35x  slower
       turbostreamer:      351.6 i/s - 1.45x  slower
alba_with_transformation:      256.1 i/s - 1.99x  slower
         jserializer:      252.5 i/s - 2.02x  slower
     fast_serializer:      142.3 i/s - 3.58x  slower
         blueprinter:      120.7 i/s - 4.23x  slower
               rails:      107.6 i/s - 4.74x  slower
       representable:       50.3 i/s - 10.15x  slower
          simple_ams:       36.6 i/s - 13.95x  slower
         alba_inline:       22.7 i/s - 22.43x  slower
                 ams:       16.9 i/s - 30.15x  slower

Calculating <span class="nt">-------------------------------------</span>
<span class="c"># Omitted for conciseness</span>

Comparison:
               panko:     259178 allocated
              barley:     633521 allocated - 2.44x more
       turbostreamer:     641760 allocated - 2.48x more
alba_with_transformation:     818181 allocated - 3.16x more
                alba:     818241 allocated - 3.16x more
         jserializer:     822281 allocated - 3.17x more
        barley_cache:     849521 allocated - 3.28x more
     fast_serializer:    1470121 allocated - 5.67x more
         blueprinter:    2297921 allocated - 8.87x more
         alba_inline:    2736041 allocated - 10.56x more
               rails:    2757857 allocated - 10.64x more
                 ams:    4713401 allocated - 18.19x more
       representable:    5151321 allocated - 19.88x more
          simple_ams:    9017033 allocated - 34.79x more</code></pre>
</div>
</div>
<div class="paragraph">
<p>With YJIT enabled, Barley really shines and is the first Hash serializer of the group.
There are <a href="https://github.com/MoskitoHero/barley/tree/main/benchmarks#readme">more benchmarks to be found in the Barley repository</a>.
Barley is very simple in its implementation and allocates very little. I believe that it is what makes it so fast. I’ll write another post on how I optimized it in version 0.9.0.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="the-features"><a class="anchor" href="#the-features"></a>The Features</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Barley provides:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>a clear DSL to define your serializer</p>
</li>
<li>
<p>inline / block sub-serializer definition</p>
</li>
<li>
<p>russian-doll caching</p>
</li>
<li>
<p>type-checking with <a href="https://dry-rb.org/gems/dry-types/main/">dry-types</a></p>
</li>
<li>
<p>context handling</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>This is how Barley works:</p>
</div>
<div class="sect2">
<h3 id="in-the-model-you-want-to-serialize"><a class="anchor" href="#in-the-model-you-want-to-serialize"></a>In the model you want to serialize</h3>
<div class="paragraph">
<p>First, include the Barley::Serializable concern in your model.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="c1"># /app/models/user.rb</span>
<span class="k">class</span> <span class="nc">User</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
  <span class="kp">include</span> <span class="no">Barley</span><span class="o">::</span><span class="no">Serializable</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Once you include this module, Barley will look for a UserSerializable class in your codebase, so you need to define one, typically in the app/serializable directory. It should inherit from Barley::Serializer</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="c1"># /app/serializers/user_serializer.rb</span>
<span class="k">class</span> <span class="nc">UserSerializer</span> <span class="o">&lt;</span> <span class="no">Barley</span><span class="o">::</span><span class="no">Serializer</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Once this is set up, the as_json method on your model will return what you have defined in your serializer class.
You can also chose to use a different serializer to render your data. You can use the serializer method in your model. This method also allows you to pass a cache argument.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="c1"># /app/models/user.rb</span>
<span class="k">class</span> <span class="nc">User</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
  <span class="kp">include</span> <span class="no">Barley</span><span class="o">::</span><span class="no">Serializable</span>

  <span class="n">serializer</span> <span class="no">MyCustomSerializer</span><span class="p">,</span> <span class="ss">cache: </span><span class="kp">true</span>
  <span class="c1"># or</span>
  <span class="n">serializer</span> <span class="no">MyCustomSerializer</span><span class="p">,</span> <span class="ss">cache: </span><span class="p">{</span><span class="ss">expires_in: </span><span class="mi">1</span><span class="p">.</span><span class="nf">hour</span><span class="p">}</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Another way to achieve this is through the as_json method:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="no">User</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">as_json</span><span class="p">(</span><span class="ss">serializer: </span><span class="no">MyCustomSerializer</span><span class="p">,</span> <span class="ss">cache: </span><span class="p">{</span><span class="ss">expires_in: </span><span class="mi">1</span><span class="p">.</span><span class="nf">hour</span><span class="p">})</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Please note that Barley overrides the as_json method and does not provide it with standard only, include or except arguments. This is because the purpose of this serializer is precisely to do that job. The root argument works, though.</p>
</div>
</div>
<div class="sect2">
<h3 id="defining-the-serializer"><a class="anchor" href="#defining-the-serializer"></a>Defining the serializer</h3>
<div class="paragraph">
<p>Let’s go back to the serializer class. Barley provides a simple DSL that allows you to define your JSON in a readable and maintainable way. Here is an example definition that covers most use cases:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="c1"># /app/serializers/user_serializer.rb</span>
<span class="k">class</span> <span class="nc">UserSerializer</span> <span class="o">&lt;</span> <span class="no">Barley</span><span class="o">::</span><span class="no">Serializer</span>

  <span class="n">attributes</span> <span class="ss">id: </span><span class="no">Types</span><span class="o">::</span><span class="no">Strict</span><span class="o">::</span><span class="no">Integer</span><span class="p">,</span> <span class="ss">:name</span>

  <span class="n">attribute</span> <span class="ss">:email</span>
  <span class="n">attribute</span> <span class="ss">:value</span><span class="p">,</span> <span class="ss">type: </span><span class="no">Types</span><span class="o">::</span><span class="no">Coercible</span><span class="o">::</span><span class="no">Integer</span>

  <span class="n">many</span> <span class="ss">:posts</span>

  <span class="n">one</span> <span class="ss">:group</span><span class="p">,</span> <span class="ss">serializer: </span><span class="no">CustomGroupSerializer</span>

  <span class="n">many</span> <span class="ss">:related_users</span><span class="p">,</span> <span class="ss">key: :friends</span><span class="p">,</span> <span class="ss">cache: </span><span class="kp">true</span>

  <span class="n">one</span> <span class="ss">:profile</span><span class="p">,</span> <span class="ss">cache: </span><span class="p">{</span> <span class="ss">expires_in: </span><span class="mi">1</span><span class="p">.</span><span class="nf">day</span> <span class="p">}</span> <span class="k">do</span>
    <span class="n">attributes</span> <span class="ss">:avatar</span><span class="p">,</span> <span class="ss">:social_url</span>

    <span class="n">attribute</span> <span class="ss">:badges</span> <span class="k">do</span>
      <span class="n">object</span><span class="p">.</span><span class="nf">badges</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:display_name</span><span class="p">)</span>
    <span class="k">end</span>
  <span class="k">end</span>

<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>For type-checking to work, you need to define a Types module like this, somewhere in your codebase:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="k">module</span> <span class="nn">Types</span>
  <span class="kp">include</span> <span class="no">Dry</span><span class="o">.</span><span class="no">Types</span><span class="p">()</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="sect3">
<h4 id="1-attributes"><a class="anchor" href="#1-attributes"></a>1. Attributes</h4>
<div class="paragraph">
<p>They can be defined as an array with the plural attributes method. It can be an array of symbols, or an array of hashes in the key: TypeDefinition pattern, or a mix of both.
They can also be defined with a singular attribute method, that accepts a type keyword argument.
If type is included in an attribute’s definition, it will raise an error. You can use types to coerce types and define constraints. Please refer to the <a href="https://dry-rb.org/gems/dry-types/main/">dry-types</a> gem for more options.
If you give a block to the attribute method, you can also return anything you like. You have the object object at hand that refers to the serialized model.</p>
</div>
</div>
<div class="sect3">
<h4 id="2-associations"><a class="anchor" href="#2-associations"></a>2. Associations</h4>
<div class="paragraph">
<p>They are defined with the one or many methods.
It will use the default serializer if no argument is given. You can provide it with a serializer and a cache option.
These methods do not provide a type argument, as it makes no sense to do so.
You can also decide to give it a block, that allows you to define the association’s serializer inline.</p>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="wrapping-it-up"><a class="anchor" href="#wrapping-it-up"></a>Wrapping it up</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The Barley gem’s goal is to provide a simple DSL to define your JSON payloads with a type-proof option, and render it in a very fast and efficient manner. It is probably not as feature-packed as other serializers available to the rails community, but it is a no-brainer, yet powerful option that you should consider if you are looking for performance and simplicity.
I’d be happy to see more projects starting to adopt it and would love the community to provide optimizations to make it even better.</p>
</div>
</div>
</div>
]]></content:encoded></item><item><title>Set up Ghost with Podman on a VPS</title><link>/post/2025-05-06-set-up-ghost-with-podman-on-a-vps/</link><pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate><guid>/post/2025-05-06-set-up-ghost-with-podman-on-a-vps/</guid><description>A step-by-step walkthrough for running Ghost on a VPS using Podman quadlets backed by Systemd, with MySQL and Caddy as a reverse proxy.</description><content:encoded><![CDATA[<div class="paragraph">
<p>I recently moved this website / blog from Jekyll to <a href="https://ghost.org/">Ghost</a>, just to try it out after hearing quite a lot of nice buzz about it lately.
I had this small personal VPS at hand, so I decided to give it some space there, and use <a href="https://podman.io/">Podman</a> (and its <em>quadlets</em> feature) to set it up. This leverages Systemd to run your docker images. Here is a quick walkthrough, should anyone be interested:
Obviously, you need a VPS with SSH access. It must have its firewall open to the web on port 443. My VPS is running Ubuntu, so my install commands will reflect this. I also chose to use root mode to run the quadlets, but there are other options available.</p>
</div>
<div class="sect2">
<h3 id="setup"><a class="anchor" href="#setup"></a>Setup</h3>
<div class="paragraph">
<p>First, <a href="https://podman.io/docs/installation">install Podman</a>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="nb">sudo </span>apt <span class="nb">install </span>podman podman-docker podman-toolbox</code></pre>
</div>
</div>
<div class="paragraph">
<p>Pull the required images from docker:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="nb">sudo </span>podman pull docker.io/mysql:8
<span class="nb">sudo </span>podman pull docker.io/ghost:5-alpine
<span class="nb">sudo </span>podman pull docker.io/caddy:alpine</code></pre>
</div>
</div>
</div>
<div class="sect1">
<h2 id="the-systemd-services"><a class="anchor" href="#the-systemd-services"></a>The Systemd services</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Create a MySQL container file at /etc/containers/systemd/mysql.container with this content:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>Unit]
<span class="nv">Description</span><span class="o">=</span>Blog MySQL Container
<span class="nv">After</span><span class="o">=</span>network-online.target

<span class="o">[</span>Container]
<span class="nv">ContainerName</span><span class="o">=</span>blog-mysql
<span class="nv">AddCapability</span><span class="o">=</span>SYS_NICE
<span class="nv">Image</span><span class="o">=</span>docker.io/mysql:8
<span class="nv">Volume</span><span class="o">=</span>/srv/containers/blog/mysql/var/lib/mysql:/var/lib/mysql:Z
<span class="nv">Pod</span><span class="o">=</span>blog.pod
<span class="nv">Secret</span><span class="o">=</span>blog_db_name,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>MYSQL_DATABASE
<span class="nv">Secret</span><span class="o">=</span>blog_db_user,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>MYSQL_USER
<span class="nv">Secret</span><span class="o">=</span>blog_db_password,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>MYSQL_PASSWORD
<span class="nv">Secret</span><span class="o">=</span>blog_db_rootpassword,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>MYSQL_ROOT_PASSWORD

<span class="o">[</span>Service]
<span class="nv">Restart</span><span class="o">=</span>always

<span class="o">[</span>Install]
<span class="nv">WantedBy</span><span class="o">=</span>default.target
<span class="nv">RequiredBy</span><span class="o">=</span>blog-ghost.service</code></pre>
</div>
</div>
<div class="paragraph">
<p>Create a Ghost container file at /etc/containers/systemd/ghost.container
Do not forget to change yourdomain.com with your domain.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>Unit]
<span class="nv">Description</span><span class="o">=</span>Ghost Container
<span class="nv">After</span><span class="o">=</span>blog-db.service

<span class="o">[</span>Container]
<span class="nv">ContainerName</span><span class="o">=</span>blog-ghost
<span class="nv">Environment</span><span class="o">=</span><span class="nv">database__client</span><span class="o">=</span>mysql <span class="nv">database__connection__host</span><span class="o">=</span>blog-mysql <span class="nv">url</span><span class="o">=</span>http://yourdomain.com
<span class="nv">Image</span><span class="o">=</span>docker.io/ghost:5-alpine
<span class="nv">Pod</span><span class="o">=</span>blog.pod
<span class="nv">Secret</span><span class="o">=</span>blog_db_name,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>database__connection__database
<span class="nv">Secret</span><span class="o">=</span>blog_db_user,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>database__connection__user
<span class="nv">Secret</span><span class="o">=</span>blog_db_password,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>database__connection__password
<span class="nv">Volume</span><span class="o">=</span>/srv/containers/blog/var/lib/ghost/content:/var/lib/ghost/content:Z

<span class="o">[</span>Service]
<span class="nv">Restart</span><span class="o">=</span>always

<span class="o">[</span>Install]
<span class="nv">WantedBy</span><span class="o">=</span>default.target</code></pre>
</div>
</div>
<div class="paragraph">
<p>You need to set up the secrets used in these two files to access MySQL. Let’s do it:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="nb">printf</span> <span class="si">$(</span><span class="nb">tr</span> <span class="nt">-dc</span> A-Za-z0-9 &lt;/dev/urandom | <span class="nb">head</span> <span class="nt">-c</span> 13<span class="si">)</span> | <span class="nb">sudo </span>podman secret create blog_db_rootpassword -
<span class="nb">printf</span> <span class="si">$(</span><span class="nb">tr</span> <span class="nt">-dc</span> A-Za-z0-9 &lt;/dev/urandom | <span class="nb">head</span> <span class="nt">-c</span> 13<span class="si">)</span> | <span class="nb">sudo </span>podman secret create blog_db_password -
<span class="nb">printf</span> <span class="s2">&#34;gdb&#34;</span> | <span class="nb">sudo </span>podman secret create blog_db_name -
<span class="nb">printf</span> <span class="s2">&#34;guser&#34;</span> | <span class="nb">sudo </span>podman secret create blog_db_user -</code></pre>
</div>
</div>
<div class="paragraph">
<p>You also need to have the files stored on your filesystem, so let’s create the directories that are mounted in the images:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="nb">sudo mkdir</span> <span class="nt">-p</span> /srv/containers/blog/var/lib/ghost/content
<span class="nb">sudo mkdir</span> <span class="nt">-p</span> /srv/containers/blog/mysql/var/lib/mysql</code></pre>
</div>
</div>
<div class="paragraph">
<p>Now, add a blog.network file at /etc/containers/systemd/blog.network</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>Unit]
<span class="nv">Description</span><span class="o">=</span>Custom blog Podman network

<span class="o">[</span>Network]
<span class="nv">NetworkName</span><span class="o">=</span>blog
<span class="nv">Driver</span><span class="o">=</span>bridge</code></pre>
</div>
</div>
<div class="paragraph">
<p>And a blog.pod file at /etc/containers/systemd/blog.pod</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>Unit]
<span class="nv">Description</span><span class="o">=</span>Blog Pod

<span class="o">[</span>Pod]
<span class="nv">PodName</span><span class="o">=</span>blog
<span class="nv">Network</span><span class="o">=</span>blog.network
<span class="nv">PublishPort</span><span class="o">=</span>3001:2368</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="caddy-set-up"><a class="anchor" href="#caddy-set-up"></a>Caddy set up</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Let’s use <a href="https://caddyserver.com/">Caddy</a> to serve the app to the internet. <em>I chose to leave Caddy out of the blog pod context because I use it to proxy other toy servers on that VPS.</em>
Create the service file at /etc/containers/systemd/caddy.container</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>Container]
<span class="nv">ContainerName</span><span class="o">=</span>caddy
<span class="nv">Image</span><span class="o">=</span>docker.io/caddy:alpine
<span class="nv">PublishPort</span><span class="o">=</span>80:80
<span class="nv">PublishPort</span><span class="o">=</span>443:443
<span class="nv">Volume</span><span class="o">=</span>/srv/containers/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
<span class="nv">Volume</span><span class="o">=</span>/srv/containers/caddy/data:/data
<span class="nv">Volume</span><span class="o">=</span>/srv/containers/caddy/config:/config
<span class="nv">Network</span><span class="o">=</span>host

<span class="o">[</span>Service]
<span class="nv">Restart</span><span class="o">=</span>always

<span class="o">[</span>Install]
<span class="nv">WantedBy</span><span class="o">=</span>default.target</code></pre>
</div>
</div>
<div class="paragraph">
<p>And create the Caddy directory:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="nb">sudo mkdir</span> <span class="nt">-p</span> /srv/containers/caddy</code></pre>
</div>
</div>
<div class="paragraph">
<p>Add a Caddyfile at /srv/containers/caddy/Caddyfile with the following content:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">{</span>
    <span class="c"># email to use on Let&#39;s Encrypt</span>
    email your@email.com
<span class="o">}</span>

www.yourdomain.com <span class="o">{</span>
        redir https://yourdomain.com<span class="o">{</span>uri<span class="o">}</span>
<span class="o">}</span>

yourdomain.com <span class="o">{</span>
  reverse_proxy 127.0.0.1:3001
<span class="o">}</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Do not forget to update the file with your email and domain.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="go-live"><a class="anchor" href="#go-live"></a>Go live</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Now let’s open the gates…​</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="nb">sudo </span>systemctl daemon-reload
<span class="nb">sudo </span>systemctl <span class="nb">enable </span>caddy
<span class="nb">sudo </span>systemctl start caddy
<span class="nb">sudo </span>systemctl <span class="nb">enable </span>blog-pod
<span class="nb">sudo </span>systemctl start blog-pod</code></pre>
</div>
</div>
<div class="paragraph">
<p>And visit yourdomain.com/ghost to start blogging!</p>
</div>
</div>
</div>
]]></content:encoded></item><item><title>Working For Your Manager’s Success</title><link>/post/2025-03-18-working-for-your-manager-success/</link><pubDate>Tue, 18 Mar 2025 00:00:00 +0000</pubDate><guid>/post/2025-03-18-working-for-your-manager-success/</guid><description>Estimates are inevitable in software development, but committing to deadlines — like a mechanic does — requires honesty with your manager rather than optimism to impress them.</description><content:encoded><![CDATA[<div class="paragraph">
<p>As software developers, we are constantly tasked with estimating the time it will take to complete tasks or deliver a feature. However, estimating the time and effort required for software development is notoriously difficult. The unpredictability of technical challenges, changing requirements, and unforeseen obstacles often means that our best efforts to estimate can still lead to frustration when things don’t go according to plan.
The idea of writing this post came to my mind as I was reading Robert C. Martin’s book <em>The Clean Coder</em>, in which he explains that estimates are inherently imprecise. This is particularly true in the software industry, where problems are often complex and the hurdles we face are, by essence, unpredictable. While we may have a rough idea of how long a task will take, there is no way to foresee all the complications that might arise along the way.
However, what true professionals do, as opposed to merely estimating, is committing to deadlines. According to Robert C. Martin, while we can estimate internally, we must also commit to a specific date or deadline. This level of responsibility requires maturity and accountability. It’s one thing to give an estimate, but it’s another to commit to a date, knowing the challenges that lie ahead. It is this commitment, rather than just an estimate, that we, as software developers, must learn to master.</p>
</div>
<div class="sect2">
<h3 id="the-analogy-of-mechanics"><a class="anchor" href="#the-analogy-of-mechanics"></a>The Analogy of Mechanics</h3>
<div class="paragraph">
<p>As I was thinking about this, the analogy of taking a car to the mechanic came to mind. When you leave your car for repairs, the mechanic will typically provide you with an estimate of when the repair will be completed. If it’s a simple task, such as replacing a part, the mechanic might tell you that it will be ready by the end of the day. For more complicated repairs, they might say it will take a few days.
In my experience, mechanics tend to give themselves a bit of extra time when providing an estimate. They know that things can go wrong, such as parts not arriving on time, unexpected issues cropping up, or complications that could delay the work. This is why they often tell you to come back at the end of the afternoon, even if the task is just a matter of one hour, or add an extra day or two to the estimate for longer operations, ensuring that they are not overly optimistic and leaving room for uncertainty. While this may occasionally lead to a situation where the repair takes a bit longer than expected, as a customer, I am usually understanding, even if it means that I have to make arrangements to reorganize a few things. After all, the mechanic has communicated the potential for delays and has made a realistic commitment.
This transparency and realistic commitment is what I believe the software development industry lacks. In many cases, as developers, we are hesitant to provide commitments that include the necessary margin for error. Why? Are mechanics naturally gifted in this area? What is their secret weapon?</p>
</div>
</div>
<div class="sect2">
<h3 id="trying-to-please-managers"><a class="anchor" href="#trying-to-please-managers"></a>Trying to Please Managers</h3>
<div class="paragraph">
<p>In the software industry, the primary individuals we make commitments to are often our managers. Unlike mechanics, who treat their customers as…​ customers—​meaning they are focused on customer satisfaction—​developers often approach their managers with the goal of pleasing, or impressing them. After all, they’re the ones who get to decide for your bonus…​ So we make a bet. And we keep making it day after day, week after week, crossing fingers and hoping for the best. But as we do this, we may downplay the risks or challenges involved in a project, providing overly optimistic estimates in an attempt to please them. The issue here is that this can lead to broken promises, missed deadlines, and a lack of trust, not to mention the dreaded burn-out. Quite the opposite of the intended effect.
The core issue is that we do not view our managers as “customers” in the traditional sense. In the same way a mechanic is not trying to impress the car owner but instead focuses on delivering a satisfactory repair, we should aim to satisfy our managers, not merely impress them. The truth is, when we try to please or impress our managers, we are really just trying to satisfy ourselves. Satisying your managers may seem less rewarding in the short term, but trust me, it will prove the best option in the long run: by focusing on their satisfaction rather than attempting to please them, we can set more realistic expectations and foster a more honest and transparent working relationship.</p>
</div>
</div>
<div class="sect2">
<h3 id="manager-success"><a class="anchor" href="#manager-success"></a>Manager Success</h3>
<div class="paragraph">
<p>So, how can we improve this dynamic? The answer lies in a simple word: Honesty.
Start a conversation with your manager. In fact, it might be helpful for teams to have a “Manager Success Index” (similar to a customer success index) to gauge what their managers truly want and expect. Having a candid conversation with your manager about their expectations and priorities can help align your commitments with what is realistically achievable.
By focusing on satisfaction over impression, we can build a more honest, transparent, and effective working environment. Most managers (except some old-school, die-hard, heartless / insecure ones — we know them)  will appreciate knowing that their developers are committed to delivering quality work, even if it takes a little extra time. In the end, this approach not only leads to better outcomes but also strengthens the trust and professional relationship between developers and managers.</p>
</div>
</div>
]]></content:encoded></item><item><title>From Chaos to Clarity: My Journey with ADHD, TickTick, and PARA</title><link>/post/2024-12-16-from-chaos-to-clarity-my-journey-with-adhd-ticktick-and-para/</link><pubDate>Mon, 16 Dec 2024 00:00:00 +0000</pubDate><guid>/post/2024-12-16-from-chaos-to-clarity-my-journey-with-adhd-ticktick-and-para/</guid><description>How I use TickTick and the PARA method to manage ADHD and transform daily chaos into a structured, productive second brain.</description><content:encoded><![CDATA[<div class="paragraph">
<p>I have <a href="https://en.wikipedia.org/wiki/Attention_deficit_hyperactivity_disorder" rel="noopener" target="_blank">ADHD</a>.</p>
</div>
<div class="paragraph">
<p>If you want to learn more about what it is, <a href="https://youtu.be/cx13a2-unjE?feature=shared" rel="noopener" target="_blank">check out this video</a>. The TL;DR is that my attention doesn’t work like most people’s. It’s either scattered in a thousand directions or laser-focused on one topic.</p>
</div>
<div class="paragraph">
<p>I’m the guy who goes to the store for milk and comes home without it—​not because I’m stupid, tired, or forgetful. It’s not a one-off thing; I have an attention disorder.</p>
</div>
<div class="sect2">
<h3 id="the-challenge"><a class="anchor" href="#the-challenge"></a>The challenge</h3>
<div class="paragraph">
<p>This disorder impacts several areas of my life in big ways. I struggle with switching contexts, planning ahead, and making decisions in certain situations.</p>
</div>
<div class="paragraph">
<p>Before I was diagnosed, I knew I lacked the organizational skills I saw in others. I couldn’t seem to finish things, at least not in the long term. I thought I was lazy and blamed my procrastination on that. But here’s the thing—​I’m not lazy. I work incredibly hard. Sometimes I get so deeply absorbed in my work that I lose all track of time. I’ve gone hours, days, and even weeks working almost non-stop.</p>
</div>
<div class="paragraph">
<p>So, I started looking for solutions to compensate for my lack of organization. I explored so many techniques and apps that I became a bit of an expert in task management and time tracking. I needed the right tool.</p>
</div>
<hr/>
</div>
<div class="sect2">
<h3 id="finding-a-solution"><a class="anchor" href="#finding-a-solution"></a>Finding a solution</h3>
<div class="paragraph">
<p>One key feature I desperately wanted was geofenced notifications—​reminders that pop up on my phone when I’m at a specific place. Things like, <em>&#34;`Buy milk when you’re near the supermarket`&#34;</em> or <em>&#34;`Pick up that tool you left at Mom’s house.`&#34;</em></p>
</div>
<div class="paragraph">
<p>That’s how I discovered TickTick <a href="https://ticktick.com/home?c=xo3basbk" rel="noopener" target="_blank">(referal link)</a>. It has geofenced notifications—​and so much more.</p>
</div>
<div class="paragraph">
<p>Forget apps like OmniFocus, iGTD, Wunderlist, Microsoft To Do, Things, or Todoist — the list goes on. You can even forget about Org-Mode, Obsidian or your calendar app. TickTick replaces them all in one sleek, integrated app. It’s packed with features but somehow avoids feeling bloated.</p>
</div>
<div class="paragraph">
<p>TickTick isn’t just a task manager; it syncs seamlessly with Google Calendar, tracks habits, and even includes an <a href="https://en.wikipedia.org/wiki/Time_management#The_Eisenhower_Method" rel="noopener" target="_blank">Eisenhower matrix</a>. It lets you manage your time with tools like Pomodoro and habit tracking. The UI/UX is clean, intuitive, and accessible. You can assign time and duration to tasks and see them alongside calendar events. This is crucial because tasks often sit ignored in a list until you schedule them. TickTick makes it easy to plan your day and slot tasks into available time.</p>
</div>
<div class="paragraph">
<p>It supports markdown for notes, which you can link to tasks, projects, or anything else. I’m writing this in TickTick right now! You can record audio notes, create templates, and more. If you can think of a feature, it’s probably there.</p>
</div>
<div class="paragraph">
<p>And it’s available everywhere: web, macOS, iOS, Android, Windows, Linux. Name a platform, it’s got you covered.</p>
</div>
<div class="imageblock">
<div class="content">
<img src="/assets/img/ticktick.webp" alt="ticktick"/>
</div>
</div>
<div class="paragraph">
<p>I’m not going to list all of TickTick’s features here—​you can <a href="https://www.youtube.com/@GetTickTick/featured" rel="noopener" target="_blank">watch these videos</a> for a rundown. But I will say this: the TickTick team is responsive and constantly adding new features.</p>
</div>
</div>
<div class="sect2">
<h3 id="the-need-for-a-framework"><a class="anchor" href="#the-need-for-a-framework"></a>The need for a framework</h3>
<div class="paragraph">
<p>Finding a tool as versatile as TickTick was a huge relief. It became a central place to store my ideas, track my habits, and manage my to-dos without wasting hours tinkering (as I did with Obsidian or Logseq). But having a great tool wasn’t the end of my journey.</p>
</div>
<div class="paragraph">
<p>With TickTick, I had a supercar—​but no idea how to drive it.</p>
</div>
<div class="paragraph">
<p>I started organizing my tasks into folders: one for work, one for personal tasks, each with lists, journals, and notes. But it felt restrictive. My real challenge wasn’t just managing work and family—​it was making them work together. Where did I save links I found online? Some were project-specific; others had broader relevance. And what about daily chores like taking out the trash every Tuesday night? How did those coexist with bigger projects like building a summer kitchen or fixing the sewage?</p>
</div>
<div class="paragraph">
<p>I spent more time deciding where to put things than actually getting things done.</p>
</div>
<div class="paragraph">
<p>I have one brain, not separate compartments for work, family, or chores. If my system was going to extend my brain, it couldn’t treat tasks and responsibilities as if they never overlapped.</p>
</div>
</div>
<div class="sect2">
<h3 id="the-para-method"><a class="anchor" href="#the-para-method"></a>The PARA method</h3>
<div class="paragraph">
<p>That’s when I had my second epiphany: I discovered the <a href="https://fortelabs.com/blog/para/" rel="noopener" target="_blank">PARA method</a>.</p>
</div>
<div class="paragraph">
<p>I don’t remember exactly how I stumbled upon it, but it changed everything. There’s a <a href="https://www.buildingasecondbrain.com/para" rel="noopener" target="_blank">book</a> and <a href="https://fortelabs.com/blog/para/" rel="noopener" target="_blank">website</a> dedicated to it, and <a href="https://youtu.be/T6Mfl1OywM8?feature=shared" rel="noopener" target="_blank">this short video explains the basics</a>. PARA requires you to organize your tasks and notes into 4 categories. Here’s the gist:</p>
</div>
<div class="sect3">
<h4 id="1-projects"><a class="anchor" href="#1-projects"></a>1. Projects</h4>
<div class="paragraph">
<p>Short-term endeavors with specific deadlines, like buying Christmas presents, learning a new programming language, or preparing a work presentation. Each project is a task list.</p>
</div>
</div>
<div class="sect3">
<h4 id="2-areas-of-responsibility"><a class="anchor" href="#2-areas-of-responsibility"></a>2. Areas of Responsibility</h4>
<div class="paragraph">
<p>Ongoing areas of your life, like family, work, side projects, or the school parent association. These aren’t short-term—​they’re about maintaining responsibilities. I use this section for everyday tasks, reminders, errands, and checklists. Each area gets its own task list in TickTick.</p>
</div>
</div>
<div class="sect3">
<h4 id="3-resources"><a class="anchor" href="#3-resources"></a>3. Resources</h4>
<div class="paragraph">
<p>This section stores notes, not tasks. I use it for writing ideas, programming research, meditation insights, and so on.</p>
</div>
</div>
<div class="sect3">
<h4 id="4-archive"><a class="anchor" href="#4-archive"></a>4. Archive</h4>
<div class="paragraph">
<p>Anything that’s no longer relevant goes here. TickTick already has an archive feature, so there’s no need to set anything up. It helps me stay focused on what matters now.<iframe width="740" height="415" src="https://www.youtube.com/embed/NY7TV7o7XjM?si=vGQ2zXxbQYKj0_52" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe></p>
</div>
</div>
</div>
<div class="sect2">
<h3 id="the-multi-threaded-brain"><a class="anchor" href="#the-multi-threaded-brain"></a>The multi-threaded brain</h3>
<div class="paragraph">
<p>The power of the PARA method lies in its natural alignment with how we think about life. Deciding where something belongs becomes effortless. When I started experimenting with PARA, it felt like a weight lifted off my shoulders.</p>
</div>
<div class="paragraph">
<p>Making a clear distinction between <em>Projects</em> and <em>Areas of Responsibility</em> was that kind of <em>&#34;a-ha&#34;</em> moment. It seems so obvious, yet I had never framed it that way in my mind. I find it hard to explain, but the burden and anxiety generated by these never-ending lists disappeared as I was able to make a simple difference between what is a short-term goal and an on-going, background task.</p>
</div>
<div class="paragraph">
<p>I also felt relieved to be able to mix personal and work projects together. After all, when a work project is on my mind, it does not vanish from my brain at 5.00pm sharp when I close the lid of my laptop…​ with ADHD, projects tends to stick around all the time.</p>
</div>
<div class="paragraph">
<p>My brain is multi-threaded. Now I Just face it, I acknowledge it instead of trying to force it to jump through unnatural and convoluted hoops. And it makes things clearer.</p>
</div>
<div class="paragraph">
<p>Everyone’s organizational style is personal, but if you’re unsatisfied with how you handle things now, I recommend trying PARA. Pairing it with TickTick’s flexibility transformed my life. <a href="https://ticktick.com/r?c=xo3basbk" target="&lt;em&gt;blank">TickTick</a> isn’t just my second brain—​it _is my brain, digitized.</p>
</div>
<div class="paragraph">
<p><br/>
<em>If you’d like to try TickTick, I recommend using my <a href="https://ticktick.com/home?c=xo3basbk" rel="noopener" target="_blank">referral link</a>. It will take you directly to the registration page, and by signing up, you’ll help support my work at no extra cost to you.</em></p>
</div>
</div>
]]></content:encoded></item><item><title>Track and debug your ActiveRecord SQL queries with annotations</title><link>/post/2024-12-10-annotating-sql-queries-for-debugging-purposes/</link><pubDate>Tue, 10 Dec 2024 00:00:00 +0000</pubDate><guid>/post/2024-12-10-annotating-sql-queries-for-debugging-purposes/</guid><description>Embed contextual comments directly into your SQL queries using ActiveRecord’s annotate method, making log analysis and debugging far easier.</description><content:encoded><![CDATA[<div class="paragraph">
<p>I recently <a href="https://blog.saeloun.com/2020/01/29/rails-6-adds-activerecord-relation-annotate-for-adding-sql-comments-to-activerecord-relation-queries/">discovered</a> a handy feature <a href="https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-annotate">introduced in Rails 6</a>. This feature allows you to annotate your SQL queries with comments directly embedded in the query itself. I know, I’m a bit late to the party, but I suspect I’m not the only one, so here’s a quick rundown.</p>
</div>
<div class="paragraph">
<p>In our app, we have a deeply nested hash called <code>public_data</code> that’s often used to render a record’s JSON payload, along with its associations, in the controller.</p>
</div>
<div class="paragraph">
<p>This <code>public_data</code> hash is built from a mix of attributes and model methods. However, it also calls the <code>public_data</code> methods of associated models, which might, in turn, call back to the original model’s <code>public_data</code>. This circular complexity has been a challenge, and while we’ve started transitioning to the <a href="https://github.com/MoskitoHero/barley">Barley serializer gem</a> for better structure, we still rely on this method in parts of our codebase for backward compatibility.</p>
</div>
<div class="paragraph">
<p>So when <code>@user.public_data</code> is called in the controller, it often triggers a long chain of database queries. Tracking down where a specific relation is being called—​whether in <code>User</code>, <code>Profile</code>, or <code>Subscription</code>--can be daunting.</p>
</div>
<div class="paragraph">
<p>While Rails logs are helpful, they require sifting through the output to correlate the log entry with the SQL query. This becomes even trickier when debugging in remote environments and searching through extensive logs in tools like New Relic or CloudWatch.</p>
</div>
<div class="paragraph">
<p>Enter <code>ActiveRecord::Relation#annotate</code>…​</p>
</div>
<div class="paragraph">
<p>This simple method lets you add comments to your SQL queries, embedding contextual information directly within the query.</p>
</div>
<div class="paragraph">
<p>Here’s how it works:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">users</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">active</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">score: </span><span class="o">..</span><span class="mi">5</span><span class="p">).</span><span class="nf">annotate</span><span class="p">(</span><span class="s2">&#34;called from Admin::UsersController&#34;</span><span class="p">)</span>

<span class="c1">#&gt; SELECT * FROM `users` WHERE `last_visit_at` &gt;= &#39;2024-11-10 16:53:05&#39;</span>
<span class="c1">#  AND `score` &lt;= 5 /* called from Admin::UsersController */ ORDER BY `id` ASC</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>This provides a clear indication of where the query originated. You can also include additional context, such as the current locale, user, or order ID, depending on your needs.</p>
</div>
<div class="paragraph">
<p>For example, adding an annotation inside the <code>public_data</code> method on my <code>Subscription</code> model makes it much easier to trace where the query originated in the chain.</p>
</div>
<div class="paragraph">
<p>Another great feature is the ability to stack annotations. Let’s say we add an annotation in the <code>active</code> scope of the <code>User</code> model:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="k">class</span> <span class="nc">User</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
  <span class="n">scope</span> <span class="ss">:active</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="p">{</span> <span class="n">where</span><span class="p">(</span><span class="ss">last_visit_at: </span><span class="mi">1</span><span class="p">.</span><span class="nf">week</span><span class="p">.</span><span class="nf">ago</span><span class="o">..</span><span class="p">).</span><span class="nf">annotate</span><span class="p">(</span><span class="s2">&#34;user active in the past week&#34;</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Calling the same query as before would generate:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">users</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">active</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">score: </span><span class="o">..</span><span class="mi">5</span><span class="p">).</span><span class="nf">annotate</span><span class="p">(</span><span class="s2">&#34;called from Admin::UsersController&#34;</span><span class="p">)</span>

<span class="c1">#&gt; SELECT * FROM `users` WHERE `last_visit_at` &gt;= &#39;2024-11-10 16:53:05&#39;</span>
<span class="c1">#  AND `score` &lt;= 5 /* user active in the past week */ /* called from Admin::UsersController */</span>
<span class="c1">#  ORDER BY `id` ASC</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Neat, right? This approach brings more clarity and structure to your logs, making them easier to query and group in log viewers. Additionally, these annotations appear in your database logs, offering a seamless way to correlate them with application logs.</p>
</div>
<div class="paragraph">
<p>Possible applications include:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Tracking queries in background jobs</p>
</li>
<li>
<p>Tagging controller context, headers, or anything else inside the queries</p>
</li>
<li>
<p>Debugging N+1 or slow queries</p>
</li>
<li>
<p>Expliciting the scope of queries</p>
</li>
<li>
<p>And so much more creative stuff!</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>That said, annotations come with a slight tradeoff in query readability. It’s essential to be deliberate about what and when to log, and to periodically review whether the annotations remain relevant and valuable.</p>
</div>
<div class="paragraph">
<p>Even if used exclusively for local debugging, SQL annotations are an excellent addition to your toolbox.</p>
</div>
]]></content:encoded></item><item><title>Ruby Best Practices - Method Arguments</title><link>/post/2024-01-12-ruby-best-practices-method-arguments/</link><pubDate>Fri, 12 Jan 2024 00:00:00 +0000</pubDate><guid>/post/2024-01-12-ruby-best-practices-method-arguments/</guid><description>Simple rules for ordering Ruby method arguments — put optional arguments last, and switch to keyword arguments when you have more than one optional parameter.</description><content:encoded><![CDATA[<div class="paragraph">
<p>To write readable and maintainable code, follow a few simple rules about argument ordering.</p>
</div>
<div class="sect1">
<h2 id="tip-1-optional-arguments-go-last"><a class="anchor" href="#tip-1-optional-arguments-go-last"></a>Tip 1: Optional arguments go last</h2>
<div class="sectionbody">
<div class="paragraph">
<p>When a method takes several arguments, and some of them have default values, make sure to put the optional ones at the end.
Take this method definition:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="s2">&#34;val_b&#34;</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span>
  <span class="c1"># some code...</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Looks fine at first glance. But in practice, it’s impossible to call this method with values for a and c without also specifying b. You end up writing:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">foo</span><span class="p">(</span><span class="s2">&#34;val_a&#34;</span><span class="p">,</span> <span class="s2">&#34;val_b&#34;</span><span class="p">,</span> <span class="s2">&#34;val_c&#34;</span><span class="p">)</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>…​which defeats the purpose of setting a default for b if you’re forced to specify it every time. ¯<em>(ツ)</em>/¯</p>
</div>
<div class="sect2">
<h3 id="solution-move-optional-arguments-to-the-end"><a class="anchor" href="#solution-move-optional-arguments-to-the-end"></a>Solution: move optional arguments to the end</h3>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="s2">&#34;val_b&#34;</span><span class="p">)</span>
  <span class="c1"># some code...</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Now you can call:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">foo</span><span class="p">(</span><span class="s2">&#34;val_a&#34;</span><span class="p">,</span> <span class="s2">&#34;val_c&#34;</span><span class="p">)</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>No problem 😎.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="tip-2-use-keyword-arguments-aka-kwargs"><a class="anchor" href="#tip-2-use-keyword-arguments-aka-kwargs"></a>Tip 2: Use keyword arguments (aka kwargs)</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Tip 1 only gets you so far. What if you have multiple optional arguments and want to override just the last one? You’d still need to specify all the previous ones.
For example:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span> <span class="o">=</span> <span class="s2">&#34;val_c&#34;</span><span class="p">,</span> <span class="n">d</span> <span class="o">=</span> <span class="s2">&#34;val_d&#34;</span><span class="p">,</span> <span class="n">e</span> <span class="o">=</span> <span class="s2">&#34;val_e&#34;</span><span class="p">)</span>
  <span class="c1"># some code...</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Want to change only e? Too bad — you still have to write:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">foo</span><span class="p">(</span><span class="s2">&#34;val_a&#34;</span><span class="p">,</span> <span class="s2">&#34;val_b&#34;</span><span class="p">,</span> <span class="s2">&#34;val_c&#34;</span><span class="p">,</span> <span class="s2">&#34;val_d&#34;</span><span class="p">,</span> <span class="s2">&#34;my_val_e&#34;</span><span class="p">)</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Now imagine this in a large codebase:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">foo</span><span class="p">(</span><span class="n">user</span><span class="p">.</span><span class="nf">id</span><span class="p">,</span> <span class="s2">&#34;DONE&#34;</span><span class="p">,</span> <span class="kp">true</span><span class="p">,</span> <span class="kp">false</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">&#34;always&#34;</span><span class="p">,</span> <span class="kp">true</span><span class="p">,</span> <span class="n">post</span><span class="p">.</span><span class="nf">id</span><span class="p">)</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>And you’re left thinking: <em>&#34;Wait, what’s that <strong>false</strong> again? What does the <strong>1</strong> do? Why <strong>&#39;always&#39;</strong>?&#34;</em>
Not ideal, especially under pressure in a production bug fix.</p>
</div>
<div class="sect2">
<h3 id="solution-switch-to-keyword-arguments-when-you-hit-2-optional-params"><a class="anchor" href="#solution-switch-to-keyword-arguments-when-you-hit-2-optional-params"></a>Solution: switch to keyword arguments when you hit 2+ optional params</h3>
<div class="paragraph">
<p>Benefits:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Argument order doesn’t matter anymore</p>
</li>
<li>
<p>You can ignore defaults entirely</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>The method now becomes:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="ss">c: </span><span class="s2">&#34;val_c&#34;</span><span class="p">,</span> <span class="ss">d: </span><span class="s2">&#34;val_d&#34;</span><span class="p">,</span> <span class="ss">e: </span><span class="s2">&#34;val_e&#34;</span><span class="p">)</span>
  <span class="c1"># some code...</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>To change only e, you write:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">foo</span><span class="p">(</span><span class="s2">&#34;val_a&#34;</span><span class="p">,</span> <span class="s2">&#34;val_b&#34;</span><span class="p">,</span> <span class="ss">e: </span><span class="s2">&#34;my_val_e&#34;</span><span class="p">)</span></code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="ninja-level-tip-the-clean-coders-take"><a class="anchor" href="#ninja-level-tip-the-clean-coders-take"></a>Ninja-level tip — the clean coder’s take:</h2>
<div class="sectionbody">
<div class="paragraph">
<p><strong>If your method takes more than 1 argument, make everything after the first a keyword argument. Always.</strong></p>
</div>
<div class="sect2">
<h3 id="example"><a class="anchor" href="#example"></a>Example:</h3>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="k">def</span> <span class="nf">send_message_to</span><span class="p">(</span><span class="n">recipient</span><span class="p">,</span> <span class="ss">confirm: </span><span class="kp">true</span><span class="p">,</span> <span class="ss">bcc: </span><span class="s2">&#34;joe@bill.com&#34;</span><span class="p">,</span> <span class="ss">cc: </span><span class="s2">&#34;jane@doe.com&#34;</span><span class="p">)</span>
  <span class="c1"># ruby code</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Here, the first argument is obvious thanks to the method name: you’re sending a message <em>to</em> someone.
Called without options:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">send_message_to</span><span class="p">(</span><span class="s2">&#34;sam@sam.com&#34;</span><span class="p">)</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Clear and concise.
Compare a non-keyword version:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">send_message</span><span class="p">(</span><span class="s2">&#34;recipient@mail.com&#34;</span><span class="p">,</span> <span class="kp">false</span><span class="p">,</span> <span class="s2">&#34;joe@bill.com&#34;</span><span class="p">,</span> <span class="s2">&#34;paul@smith.com&#34;</span><span class="p">)</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>…​with the keyword version:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">send_message_to</span><span class="p">(</span><span class="s2">&#34;sam@sam.com&#34;</span><span class="p">,</span> <span class="ss">bcc: </span><span class="s2">&#34;paul@smith.com&#34;</span><span class="p">,</span> <span class="ss">confirm: </span><span class="kp">false</span><span class="p">)</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>If you adopt these habits, <a href="&lt;em&gt;GHOST_URL&lt;/em&gt;/writing-code-for-your-future-self">future you</a> will thank you when you revisit the code to debug or tack on yet another parameter.</p>
</div>
</div>
</div>
</div>
]]></content:encoded></item><item><title>Love your error messages as thyself</title><link>/post/2022-05-17-learn-your-error-messages-as-thyself/</link><pubDate>Tue, 17 May 2022 07:44:36 +0000</pubDate><guid>/post/2022-05-17-learn-your-error-messages-as-thyself/</guid><description>Stop ignoring error messages and start reading them — it will make you a better developer and help you understand your language from the inside out.</description><content:encoded><![CDATA[<div class="paragraph">
<p>Learn to hate, then love error messages.</p>
</div>
<div class="sect1">
<h2 id="stack-overflowoverflow"><a class="anchor" href="#stack-overflowoverflow"></a>Stack Overflow overflow</h2>
<div class="sectionbody">
<div class="paragraph">
<p>I have lost so much time on Stack Overflow.</p>
</div>
<div class="paragraph">
<p>I mean Stack Overflow is great, and software development knowledge would not be so advanced and so widespread without its great influence. What I mean is that I have often found myself repeating the endless pattern of <em>&#39;Hey, I have an error…​ Wow, so many words…​ Let’s google this. Stack Overflow has it! Let’s see what they say&#39;</em></p>
</div>
<div class="paragraph">
<p>I am not talking about the infamous Stack Overflow copy-paste syndrome, nor am I dismissing the great answers produced by the great developers out ther, but rather about the missing step in the example above: making sense of the error message myself before actually looking for a ready-made answer.</p>
</div>
<div class="paragraph">
<p>Our brain is so quick at taking shortcuts that it often makes us lazy and skip the seemingly unneeded steps our thinking needs to fully develop. Take the example of walking. Although I don’t exactly remember taking my first step in this world, I can imagine it was a movement that required so much concentration - not to say bravery - and coordination that it must have felt quite awkward at the beginning. Today, I don’t even think about it. My brain just orchestrates everything, allowing me to go for a walk and actually think about something else.</p>
</div>
<div class="paragraph">
<p>Think about the first day you typed your first computer program. Think about the way you were thinking at the time. Maybe you are currently learning computer science and many mechanisms experienced developers don’t even think about are steps you consciously take, one by one, in order to reach code quality - or even to write code that actually works.</p>
</div>
<div class="paragraph">
<p>And while learning, we all encountered bugs. The language we were coding in was talking to us with complicated words, and concepts we sometimes did not even know about. We all googled for solutions. And Stack Overflow is so well conceived and maintained that we most certainly found the answer there. And it happened again. Error messages were so hard to understand, so cryptic, sometimes. So we just kept copy-pasting the error messages, and it just kept throwing correct answers at you, which, with a little adaptation, just worked.</p>
</div>
<div class="paragraph">
<p><em>And then, your brain skipped the error-message-reading step.</em></p>
</div>
<div class="paragraph">
<p>But we don’t want to just be very, very, very talented googlers, do we?</p>
</div>
</div>
</div>
<h1 id="rtfem" class="sect0"><a class="anchor" href="#rtfem"></a>RTFEM!</h1>
<div class="paragraph">
<p>Read the f***ing error messages!</p>
</div>
<div class="paragraph">
<p>So I encourage all of you learners to actually take a long look at the error messages that are thrown right in your face just as you were expecting a successful compilation. Take the time to read them. Take the time to look at those weird symbols that show up here and there. And you know what? You may not get to the point quicker at first, but you will gain a deeper knowledge of how your language is actually built, how it works internally. You might look at your fellow programmers increase their skills faster on the surface, but your understanding of error messages will eventually turn you into a Code Jedi Master.</p>
</div>
<div class="paragraph">
<p>What made me really stop overlooking error messages was reading <em>Ruby Under A Microscope</em> by Pat Shaughnessy, a most excellent book about the way Ruby is actually written in C, the way the language parser works and the way it turns the words you write into machine-understandable concepts.</p>
</div>
<div class="paragraph">
<p>Somewhere in the first part of the book that introduces how the parser works, the author talks about language tokenisation, which is - very basically - how the parser takes words and understand how to match them with a feature. And here they were, in a code example, my long-seen-yet-so-little-known error message friends, namely <code>tSTRING</code>, <code>tCONSTANT</code> and <code>tINTEGER</code>.</p>
</div>
<div class="paragraph">
<p>If only I had known what these correspond to before, <a href="https://makandracards.com/makandra/15611-how-to-fix-unexpected-token-error-for-json-parse">error messages such as these</a> would never have had me google for an answer.</p>
</div>
<div class="paragraph">
<p>But how could I know? When you google for ruby tString, you get all sorts of results, mostly skipping the preceding t. In order to get to the real stuff, I should have taken the time to actually read the error messages and wonder:</p>
</div>
<div class="paragraph">
<p>What is the language trying to tell me?</p>
</div>
<div class="paragraph">
<p>And if that sounds awkward, just remember that your programming language is actually a computer program written by other humans, and ask yourself:</p>
</div>
<div class="paragraph">
<p>What is the author of the language telling me to look at?</p>
</div>
<div class="paragraph">
<p>Chances are he is trying to get you to look at features of the language that may not be plain to see, but a little hidden from sight. And if he is trying to have you look at these, you might just as well do it. And learn.</p>
</div>
<div class="sect1">
<h2 id="learn-to-love-your-errormessages"><a class="anchor" href="#learn-to-love-your-errormessages"></a>Learn to love your error messages</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Not only should you read error messages carefully, but you should also take the time to craft yours with love and care. Like I said in an earlier post, <a href="https://moskitohero.com/development/2021/11/21/writing-code-for-your-future-self.html">you should always try to write code with your future self in mind</a>. And I guess the same applies to error messages.</p>
</div>
<div class="paragraph">
<p>Don’t just raise <code>Error</code>, or <code>StandardError</code> in your lib. Actually write your own <code>OddError</code> class. Give it a message. Make it clear. Make it so your user - and that may be the future you - is eager to look at what you coded and understand how it is architectured.</p>
</div>
<div class="paragraph">
<p>An error message must be clear, so it requires your code to be clear, and therefore it requires your programming mind to be clear about what the code is actually doing. Error messages tell something about your self.</p>
</div>
<div class="paragraph">
<p>Error messages are the conversation between the author of a program and his users. As a programmer, you will be either one or the other, in turn, and often both at the same time.</p>
</div>
<div class="paragraph">
<p>Error messages will make you grow while <em>learning</em> software. They will make you grow while <em>using</em> software. They will make you grow while <em>writing</em> software. Do not just dismiss them and skip to the Stack Overflow step.</p>
</div>
</div>
</div>
]]></content:encoded></item><item><title>Writing code for your future self</title><link>/post/2021-11-21-writing-code-for-your-future-self/</link><pubDate>Sun, 21 Nov 2021 07:44:36 +0000</pubDate><guid>/post/2021-11-21-writing-code-for-your-future-self/</guid><description>A set of guidelines to write clean, readable code that you — and your future self — will thank you for.</description><content:encoded><![CDATA[<div class="paragraph">
<p>We all write bad code. Let’s face it. Even if we do our best at writing the most efficient and well-designed code, we all happen to write this dirty method we don’t feel so proud of and dread having to debug next month, or next year.</p>
</div>
<div class="paragraph">
<p>I’m not going to talk about that code, because it’s so easy to spot and there is no need to write blog posts about it. What I’m interested in is code which looks fine when you have finished typing, but will bite you in the back someday. It ends up being hard to maintain, extend or even understand. After years of coding, I have come up with a set of warnings that alert me whenever I am heading for a dead end.</p>
</div>
<div class="sect1">
<h2 id="meaning"><a class="anchor" href="#meaning"></a>Meaning</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Code is language. Even more so when you use a programming language such as Ruby, which is designed to be very expressive. But to me, code begins to smell when it becomes unclear what it really does.</p>
</div>
<div class="sect2">
<h3 id="names"><a class="anchor" href="#names"></a>Names</h3>
<div class="paragraph">
<p>Naming is essential. One of the thing that changes the most in my creation process is probably the name of the classes, methods and variables I design.</p>
</div>
<div class="paragraph">
<p>Humans name things they understand. And the higher the knowledge of an element, the sharper the naming. Renaming my classes in the development process is usually a good sign that I am getting a better idea of what they do or what concept they stand for.</p>
</div>
</div>
<div class="sect2">
<h3 id="method-length"><a class="anchor" href="#method-length"></a>Method length</h3>
<div class="paragraph">
<p>One of the most frequent design patterns we are taught as aspiring OOP developers is to keep methods shorter than a few lines. One caveat, however, is that code ends up scattered all over the place, and you can sometimes lose touch with the lifecycle of your data. This is especially true for classes that are used to build a data structure.</p>
</div>
<div class="paragraph">
<p>For instance, take this class that builds a <code>data</code> hash to be sent to FCM:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="k">module</span> <span class="nn">MobilePush</span>
  <span class="k">class</span> <span class="nc">FcmNotification</span> <span class="o">&lt;</span> <span class="no">BaseNotification</span>

    <span class="k">def</span> <span class="nf">launch</span>
      <span class="c1"># Bootstrap stuff...</span>
      <span class="n">push_service</span><span class="p">.</span><span class="nf">send_message</span><span class="p">(</span><span class="n">project</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span>
    <span class="k">end</span>
    <span class="kp">private</span>
      <span class="c1"># Lots of methods...</span>
      <span class="k">def</span> <span class="nf">data</span>
       <span class="p">{</span>
          <span class="ss">notification: </span><span class="n">json_notification</span><span class="p">,</span>
          <span class="ss">recipient: </span><span class="n">recipient_name</span><span class="p">,</span>
          <span class="ss">room: </span><span class="n">room_name</span>
        <span class="p">}</span>
      <span class="k">end</span>
      <span class="c1"># Lots of other methods...</span>
      <span class="k">def</span> <span class="nf">json_notification</span>
        <span class="vi">@notification</span><span class="p">.</span><span class="nf">to_json</span>
      <span class="k">end</span>
      <span class="k">def</span> <span class="nf">recipient_name</span>
        <span class="vi">@recipient</span><span class="p">.</span><span class="nf">name</span>
      <span class="k">end</span>

      <span class="c1"># Maybe a few other methods...</span>
      <span class="k">def</span> <span class="nf">room_name</span>
        <span class="vi">@room</span><span class="p">.</span><span class="nf">name</span>
      <span class="k">end</span>
      <span class="c1"># The rest of the methods...</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p><em>Note: This example is a little over-simplified for brevity. But you get the idea.</em></p>
</div>
<div class="paragraph">
<p>I wrote this thinking <em>&#34;Hey that’s smart! Everything is nicely sorted. It really looks like clean code&#34;</em>.</p>
</div>
<div class="paragraph">
<p>Then I took a few day’s break with my family. While I was away, one of my co-workers had to deal with my new code and add a new feature. But he found it unnecessarily abstracted, hard to maintain and extend. And he was right. So I refactored it to:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="k">module</span> <span class="nn">MobilePush</span>
  <span class="k">class</span> <span class="nc">FcmNotification</span> <span class="o">&lt;</span> <span class="no">BaseNotification</span>

    <span class="k">def</span> <span class="nf">launch</span>
      <span class="c1"># Bootstrap stuff...</span>
      <span class="n">push_service</span><span class="p">.</span><span class="nf">send_message</span><span class="p">(</span><span class="n">project</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span>
    <span class="k">end</span>
    <span class="kp">private</span>
      <span class="c1"># Lots of methods...</span>
      <span class="k">def</span> <span class="nf">data</span>
       <span class="p">{</span>
          <span class="ss">notification: </span><span class="vi">@notification</span><span class="p">.</span><span class="nf">to_json</span><span class="p">,</span>
          <span class="ss">recipient: </span><span class="vi">@recipient</span><span class="p">.</span><span class="nf">name</span><span class="p">,</span>
          <span class="ss">room: </span><span class="vi">@room</span><span class="p">.</span><span class="nf">name</span>
        <span class="p">}</span>
      <span class="k">end</span>
      <span class="c1"># The rest of the methods...</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>So OK, I had to swap out <code>room_name</code> for <code>@room.name</code> in two or three places in the class, because it was also used elsewhere. But I also made my code a little less abstracted. Now, my co-worker knows exactly what data is made of, at a glance.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="stories"><a class="anchor" href="#stories"></a>Stories</h2>
<div class="sectionbody">
<div class="paragraph">
<p>One thing to remember when writing code, is that it is not just meant to be effective and concise. Code is meant to be read by others. Your co-workers, your team leader, and even more crucially, by the person who will be in charge of your once-new, now-legacy codebase.</p>
</div>
<div class="sect2">
<h3 id="one-liners"><a class="anchor" href="#one-liners"></a>One-liners</h3>
<div class="paragraph">
<p>I love one-liners. They are fun, straight to the point, and they showcase the technical skills and the wit of their creators. They are to programming what punchlines, or newspaper headlines are to writing. But who wants to read a whole book written in this way?</p>
</div>
<div class="paragraph">
<p>Even though I sometimes use one-liners in my code, I only do so when I feel confident my successor - or my future self - will be able to make sense out of it easily.</p>
</div>
</div>
<div class="sect2">
<h3 id="be-explicit"><a class="anchor" href="#be-explicit"></a>Be explicit</h3>
<div class="paragraph">
<p>I believe that most code should be explicit and guide the reader with landmarks that will help him or her follow along.</p>
</div>
<div class="paragraph">
<p>For instance, instead of this kind of expression:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="no">Device</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s1">&#39;last_seen &gt; ?&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="nf">hour</span><span class="p">.</span><span class="nf">ago</span><span class="p">).</span><span class="nf">where</span><span class="p">(</span><span class="ss">logging: </span><span class="kp">true</span><span class="p">).</span><span class="nf">where</span><span class="p">(</span><span class="ss">notifications: </span><span class="kp">true</span><span class="p">).</span><span class="nf">last</span><span class="p">.</span><span class="nf">send_message</span><span class="p">(</span><span class="n">topic</span><span class="p">,</span> <span class="n">payload</span><span class="p">)</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>I would rather either use class methods, or scopes in the model, to end up with:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">device</span> <span class="o">=</span> <span class="no">Device</span><span class="p">.</span><span class="nf">active_last</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="nf">hour</span><span class="p">).</span><span class="nf">logging</span><span class="p">.</span><span class="nf">notifying</span><span class="p">.</span><span class="nf">last</span>
<span class="n">device</span><span class="p">.</span><span class="nf">send_message</span><span class="p">(</span><span class="n">topic</span><span class="p">,</span> <span class="n">payload</span><span class="p">)</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>I’m not stupid. I can understand the first example. But when parsing hundreds of lines of code to find a bug, I can look at this code and instantly tell what it does. No need to make sure I got the &lt; and the &gt; right. This was done once, on the day it was typed, and I can rely on it with confidence because each of these methods is tested and does what it says.</p>
</div>
</div>
<div class="sect2">
<h3 id="be-methodic"><a class="anchor" href="#be-methodic"></a>Be methodic</h3>
<div class="paragraph">
<p>I like to separate intents in what I do with my class. When you tell a story, you first introduce your character with a clear description. Then only you give him an active role in your story. That’s what the code example above is doing. I could also have easily  written:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="no">Device</span><span class="p">.</span><span class="nf">active_last</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="nf">hour</span><span class="p">).</span><span class="nf">logging</span><span class="p">.</span><span class="nf">notifying</span><span class="p">.</span><span class="nf">last</span><span class="p">.</span><span class="nf">send_message</span><span class="p">(</span><span class="n">topic</span><span class="p">,</span> <span class="n">payload</span><span class="p">)</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>But that’s mixing intents. This line is both querying the model and triggering an action, so you start reading the line thinking: <em>&#34;Oh, he is defining the Device&#34;</em> and finish it thinking <em>&#34;Ah. No, he is sending this device a message&#34;</em>. Your reader should never ever be misguided, even in short, simple assertions like these.</p>
</div>
<div class="paragraph">
<p>I try to separate queries from actions. They are two types of tasks and require two distinct steps in my code.</p>
</div>
<div class="paragraph">
<p>Consider these three sentences:</p>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>The man in the bar that is old and dirty is talking about yesterday’s soccer results. - messy</p>
</div>
</blockquote>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>There is a man who is talking in the dirty old bar about yesterday’s soccer results. - clearer</p>
</div>
</blockquote>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>In the dirty old bar, there is a man. He is talking about yesterday’s soccer results. - crystal clear</p>
</div>
</blockquote>
</div>
<div class="paragraph">
<p>I want my code to be crystal-clear. I want my code reviewer, my successor or my future very-tired-on-a-friday-afternoon self to perfectly get what I am doing with my code.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="tests"><a class="anchor" href="#tests"></a>Tests</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Writing tests helps me identify dependencies. Integration tests often require quite a lot of bootstrapping: a logged in user, a few related entities…​ that’s fine.</p>
</div>
<div class="paragraph">
<p>But I sometimes write tests that rely too much on other models. If you can’t test your <code>Comment</code> model without a <code>Post</code> model, then something may be too tightly coupled in the code, and the relationship between those entities needs to be inquired.</p>
</div>
<div class="paragraph">
<p>Assertions also guide me towards code clarity. I have found that when I have trouble defining an expectation in my test, it usually means that this particular method is not doing something clear enough. It either needs to be split into smaller methods or completely re-thought about.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="a-word-aboutcomments"><a class="anchor" href="#a-word-aboutcomments"></a>A word about comments</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Another sign that code needs to be refactored is comments. Comments mostly indicate that what the program does is unclear. So when I see comments in code, the first thing I do is delete them and figure out what is happening without reading them. And whenever that comes out messy, it’s a good sign that they can all be replaced by a single <code>TODO: refactor</code>.</p>
</div>
<div class="paragraph">
<p>Comments are also often a sign of an external dependency. Typically, you will get this kind of thing:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="c1"># Handle temperature &gt; 21</span>
<span class="c1"># Deal with legacy customer IDs</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>The first one might depend on the device you’re dealing with, and that dependency might be addressed by the hardware team.</p>
</div>
<div class="paragraph">
<p>The second one could be deleted by resorting to an inherited class, like <code>LegacyCustomer</code>, which could handle that kind of special user cases and keep the <code>Customer</code> class clean with a predictable and simple interface.</p>
</div>
<div class="paragraph">
<p>The only comments I keep are top comments above class declarations, Yard documentation, a few refactor TODOs and silly, fun meta about the code.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="conclusion"><a class="anchor" href="#conclusion"></a>Conclusion</h2>
<div class="sectionbody">
<div class="paragraph">
<p>I sometimes look at messy code and wonder <em>&#34;who wrote this piece of crap?&#34;</em></p>
</div>
<div class="paragraph">
<p>Let’s see…​ Git blame…​</p>
</div>
<div class="paragraph">
<p><em>Me.</em></p>
</div>
<div class="paragraph">
<p>I don’t like this feeling. I want to feel proud of my code. I want other developers, including my future self, to make sense out of it easily and instantly, and thank me for making the effort of crafting code that makes sense, is easy to debug and maintain.</p>
</div>
<div class="paragraph">
<p>Following this set of guidelines will hopefully help me down that path.</p>
</div>
</div>
</div>
]]></content:encoded></item><item><title>Lessons learned after migrating from Heroku to a kubernetes cluster</title><link>/post/2021-02-20-lessons-learned-after-migrating-from-heroku-to-a-kubernetes-cluster/</link><pubDate>Sat, 20 Feb 2021 00:00:00 +0000</pubDate><guid>/post/2021-02-20-lessons-learned-after-migrating-from-heroku-to-a-kubernetes-cluster/</guid><description>I recently migrated my company’s rails hosting from Heroku to a kubernetes cluster. It was a daunting task, but I learned a lot. Here are the lessons I learned, and how you can avoid the pitfalls I encountered.</description><content:encoded><![CDATA[<div class="paragraph">
<p>Part of the mission I was given when I arrived at my now workplace was the complete migration of our rails hosting from a Heroku service to a kubernetes solution. This was my first experience migrating a production stack, and my first experience with kubernetes as well. And just in case you’d think the task wasn’t daunting enough, the previous sole developper decided to leave for another opportunity a few weeks after my arrival. This departure was soon forgotten with the company hiring another developper who had already transitioned an app to kubernetes. So I took a course on udemy, read the kubernetes documentation thouroughly, took notes, and played around with a toy cluster. Then I set out on the transition journey.</p>
</div>
<div class="paragraph">
<p>Six months later, the Heroku-Kubernetes transition is now over. I have gained a lot of experience. And I feel it’s time to share it.</p>
</div>
<div class="sect1">
<h2 id="the-stack"><a class="anchor" href="#the-stack"></a>The stack</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Our company’s flagship product is a smart lamp that detects elderly people’s movements and activities and send an alarm whenever the fall. It is mainly used in french retirement houses, but we have received quite a lot of buzz in the silver economy field and we are about to scale worldwide, and sell our product, along with its web infrastructure, to franchised resellers. This is where Kubernetes comes as a handy solution, as it allows for quick replication and scaling of a single stack.</p>
</div>
<div class="paragraph">
<p>So the stack consists in a MQTT broker (hosted with CloudMQTT) that receives the messages sent by the smart lamps and their accessories. A sidekiq worker is then constantly polling the broker to get the messages and take action based on the content: send alerts, monitor activity, or just log temperature and so on. The rails web app is in charge of an admin interface and a few API endoints used by the mobile apps. Other connected services include Heroku Postgres for the data, Redis as the Sidekiq backend, Elasticsearch to index and search the logged MQTT payloads, logentries to track the rails log, Sentry for alerting and Sendgrid for emails.</p>
</div>
<div class="paragraph">
<p>All these services had been subscribed to as Saas from the Heroku app console.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="what-i-did-right"><a class="anchor" href="#what-i-did-right"></a>What I did right</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="i-asked-for-time"><a class="anchor" href="#i-asked-for-time"></a>I asked for time</h3>
<div class="paragraph">
<p>Given the context, that was the first thing I did. The company wanted a swift move, but I said I had to learn about the stack, get acquainted with the codebase and troubleshoot a few issues before I could get a good idea of what the implications of such a migration meant. They agreed, and gave me a few months to transition.</p>
</div>
<div class="paragraph">
<p>I can only imagine how painful it would have been to undertake such a task with a limited knowledge of the app.</p>
</div>
</div>
<div class="sect2">
<h3 id="i-asked-for-help"><a class="anchor" href="#i-asked-for-help"></a>I asked for help</h3>
<div class="paragraph">
<p>My previous devops-ish experience was limited to editing my Capistrano configuration and maintaining a few VPS. Pushing a new update at my new workplace merely consisted in typing <code>git push heroku master</code> and maybe run a migration on the server afterwards. Tuning the stack consisted in adding or removing a dyno, and changing the Saas billing plan to get more ressources.</p>
</div>
<div class="paragraph">
<p>When I started looking at the way Kubernetes is architectured, I quickly understood that I needed the help of a consultant to guide me and advise me on how to build the new stack and make wise decisions on the myriad of configuration options available. I am ever so thankful to that guy, who happened to be very friendly and patient. Kudos.</p>
</div>
</div>
<div class="sect2">
<h3 id="i-played-around-and-took-notes"><a class="anchor" href="#i-played-around-and-took-notes"></a>I played around and took notes</h3>
<div class="paragraph">
<p>This is important. I destroyed and rebuilt the cluster from scratch at least three times. Every time, I wrote down stuff in a wiki for reference. And every time, I improved the workflow and gained a lot of experience.</p>
</div>
<div class="paragraph">
<p>I deleted pods, services, recreated them, fiddled with PVCs, tried out many helm charts…​ This gave me hands-on ecxperience and allowed me to crash things a few times before knowing how to get things working.</p>
</div>
</div>
<div class="sect2">
<h3 id="i-improved-the-production-workflow"><a class="anchor" href="#i-improved-the-production-workflow"></a>I improved the production workflow</h3>
<div class="paragraph">
<p>Maybe I should say &#34;he&#34;, because that’s what the kubernetes consultant’s job.</p>
</div>
<div class="paragraph">
<p>We now have a nice, fully integrated CI flow that creates kubernetes environments for production, testing and staging. It still needs to be improved, but we are almost on par with <code>git push heroku master</code> now.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="what-i-did-wrong"><a class="anchor" href="#what-i-did-wrong"></a>What I did wrong</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Lots of things. Here are the details.</p>
</div>
<div class="sect2">
<h3 id="i-lost-time-adapting-the-stack"><a class="anchor" href="#i-lost-time-adapting-the-stack"></a>I lost time adapting the stack</h3>
<div class="paragraph">
<p>When I introduced Elasticsearch to the stack, our memory consumption went ballistic. So we decided to find an alternative. Elasticsearch is not used as an end-user-facing feature. It is used by our aftersales department and our developers. So we decided that searching the logs could take 3 seconds instead of 300ms and got rid of elasticsearch altogether.</p>
</div>
<div class="paragraph">
<p>We were wrong. Not only did I spend a lot of time researching solutions, tuning indices, partitioning tables, refactoring code, but in the end I didn’t come up with a viable alternative that could be used on a day-to-day basis by my fellow workers. So I decided to temporarily move back to Elasticsearch to transition, and do the reasearch later on. That should have been our decision from the start, because we lost plenty of man-hour money for the sake of saving a few yearly hundred bucks.</p>
</div>
</div>
<div class="sect2">
<h3 id="i-added-features"><a class="anchor" href="#i-added-features"></a>I added features</h3>
<div class="paragraph">
<p>When you are given a new toy, it is tempting to try it in every situation that pops about.</p>
</div>
<div class="paragraph">
<p>Don’t.</p>
</div>
<div class="paragraph">
<p>After a few weeks, the k8s branch had diverged from master in an tremendous measure. Again, it could have gone horribly wrong, and it did to some extent, as I had to do a few live patches on day 2 after a few customers explaind that some parts of the service was not behaving as expacted.</p>
</div>
</div>
<div class="sect2">
<h3 id="i-changed-versions"><a class="anchor" href="#i-changed-versions"></a>I changed versions</h3>
<div class="paragraph">
<p>Hey. Postgres 12 is available. Let’s use it.</p>
</div>
<div class="paragraph">
<p>Maybe that’s a good idea to upgrade your database. But just keep this for a later time, once the dust is settled. I did encounter a few bugs related to that upgrade. They were not gigormous bugs. But they could have been avoided, or postponed.</p>
</div>
</div>
<div class="sect2">
<h3 id="i-moved-everything-in-one-go"><a class="anchor" href="#i-moved-everything-in-one-go"></a>I moved everything in one go</h3>
<div class="paragraph">
<p>We decided that we wanted to make our cluster totally self-contained and stop relying on Saas, except for Sentry and Sendgrid. So we went for Stolon as a high-availability Postgresql database, VerneMQ for the MQTT broker, and bitnami helm charts for Elasticsearch and Redis.</p>
</div>
<div class="paragraph">
<p>This is fine. But it is too much to handle in a single move. I had quite a lot of experience with handling a Postgres database, Redis is OK, but I was a complete newbie to elasticsearch administration. On migration day, this was the servoce that caused the most issues, and I had to reindex the 80 million logs database twice to get it right without crashing the server.</p>
</div>
<div class="paragraph">
<p>Looking back, things could have gone extremely wrong.</p>
</div>
<div class="paragraph">
<p>I should have taken things gradually:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>First, the rails app, Redis and Postgres. That’s the core of our stack. And that’s enough work for a fist pass. Fix the bugs, tune the app, and let it settle for a week or two.</p>
</li>
<li>
<p>Second, set up Grafana. I use Loki for logs, so it is a nice replacement for Logentries.</p>
</li>
<li>
<p>Then, bring in the MQTT broker. It’s easy, because you can just bridge the messages from one to another. Then that’s just a case of pointing the devices to the new URL. That’s the IoT dev team’s problem, not mine.</p>
</li>
<li>
<p>Finally, try Elasticsearch with a subset of logs. See how it behaves. Then gradually move the rest to the new infrastrucure.</p>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="tl-dr-how-you-should-do-it"><a class="anchor" href="#tl-dr-how-you-should-do-it"></a>TL; DR; How you should do it</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Don’t be too greedy, be humble and try moving things little by little. Try to keep the stack as similar as possible as the Heroku stack to start with: same software versions, same environment. Only then can you upgrade software if you wish to do so. Keep the connected Saas running, and gradually add them to your stack if that’s what you want to do. But be sure to have a proper testing and staging environment beforehand.</p>
</div>
</div>
</div>
]]></content:encoded></item><item><title>An alternative to the ActiveRecord after_initialize callbacks</title><link>/post/2020-10-08-an-alternative-to-the-activerecord-after-initialize-callbacks/</link><pubDate>Sat, 03 Oct 2020 00:23:33 +0000</pubDate><guid>/post/2020-10-08-an-alternative-to-the-activerecord-after-initialize-callbacks/</guid><description>Avoiding the callback hell in Rails by creating a simple method in the model to initialize associated records.</description><content:encoded><![CDATA[<div class="paragraph">
<p>I was playing with dynamic nested forms with Stimulus today and I stumbled upon a void left by Rails&#39; ActiveRecord callbacks.</p>
</div>
<div class="paragraph">
<p>I have this Caregiver model within a nested form and I need to create it along with a User entity in my form, to be able to populate the fields. The problem is that creating an <code>after_initialize</code> callback (even with <code>:new_record?</code> will break the general behaviour of my app, and I don’t want to run through all the specs and fix FactoryBot issues etc.</p>
</div>
<div class="paragraph">
<p>So I just decided to create a small method in my Caregiver model:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="c1"># caregiver.rb</span>

<span class="k">def</span> <span class="nf">init_with_user</span>
  <span class="nb">self</span><span class="p">.</span><span class="nf">build_user</span>
  <span class="nb">self</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Then I just need to call it in my view like this:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="erb"><span class="cp">&lt;%=</span> <span class="n">f</span><span class="p">.</span><span class="nf">fields_for</span> <span class="ss">:caregivers</span><span class="p">,</span> <span class="n">caregiver</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">init_with_user</span><span class="p">,</span> <span class="ss">child_index: </span><span class="s1">&#39;TEMPLATE_RECORD&#39;</span> <span class="k">do</span> <span class="o">|</span><span class="n">cf</span><span class="o">|</span> <span class="cp">%&gt;</span>
  <span class="cp">&lt;%=</span> <span class="n">render</span> <span class="s1">&#39;caregiver_fields&#39;</span><span class="p">,</span> <span class="ss">cf: </span><span class="n">cf</span> <span class="cp">%&gt;</span>
<span class="cp">&lt;%</span> <span class="k">end</span> <span class="cp">%&gt;</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>This makes the code in my view simpler, more explicit, while avoiding the dreaded callback hell.</p>
</div>
]]></content:encoded></item><item><title>Chaining conditions in regular expressions</title><link>/post/2020-10-03-chaining-conditions-in-regular-expressions/</link><pubDate>Sat, 03 Oct 2020 00:23:33 +0000</pubDate><guid>/post/2020-10-03-chaining-conditions-in-regular-expressions/</guid><description>Unleash the full power of regular expressions by chaining conditions together.</description><content:encoded><![CDATA[<div class="imageblock">
<div class="content">
<img src="/assets/img/regexp.jpg" alt="Regexp hell"/>
</div>
</div>
<div class="paragraph">
<p>I recently had to make a semi-complex check on a string to validate it.</p>
</div>
<div class="paragraph">
<p>Basically, I have this model method which produces a serial number based on the ID of the object. Valid returned values for this method are <code>L00000002</code>, <code>L00000587</code> or <code>L00014522</code>. In short, they should be constituted of a capital <code>L</code> followed by 8 digits composed of the <code>id</code> of the object padded with zeros.</p>
</div>
<div class="paragraph">
<p>So I had to find a way to simply created a matcher for this to use in my model spec. I could have built two expectations, one for the length, and one for the composition. But I just decided to believe that regular expressions are powerful enough to do both at the same time. So I headed to <a href="https://rubular.com">rubular</a> and started fiddling and googling around.</p>
</div>
<div class="paragraph">
<p>The solution I came up with was the following (with an <code>id</code> of <code>12</code>, for instance):</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="regexp">/^L(?=[0-9]{8}$)0*12$/</code></pre>
</div>
</div>
<div class="paragraph">
<p>What this does, is - after the starting <code>^L</code> character - create a condition matching whatever follows the <code>?=</code> inside the parentheses. If this condition is true, then the rest of the expression is read. So I just check for any digit (<code>[0-9]</code> - it could have been <code>\d</code> but readability make the first option a better fit IMHO) exacty 8 times (<code>{8}</code>) and then the end of the string with <code>$</code>. If this is matched, then I check any number of zeros (<code>0*</code>) followed by the id (<code>12</code>) and the end of the string (<code>$</code>).</p>
</div>
<div class="paragraph">
<p>Now I could use this expression in my spec, using the <code>Regexp.escape</code> method on my model’s <code>id</code> within a string interpolation:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="c1"># spec/models/my_model_spec.rb</span>
<span class="n">describe</span> <span class="s2">&#34;serial_number&#34;</span> <span class="k">do</span>
  <span class="n">let</span><span class="p">(</span><span class="ss">:my_model</span><span class="p">)</span> <span class="p">{</span> <span class="n">create</span><span class="p">(</span><span class="ss">:my_model</span><span class="p">)</span> <span class="p">}</span> <span class="c1"># I use the FactoryBot gem</span>

  <span class="n">it</span> <span class="s2">&#34;returns a valid serial number&#34;</span> <span class="k">do</span>
    <span class="n">expect</span><span class="p">(</span><span class="n">my_model</span><span class="p">.</span><span class="nf">serial_number</span><span class="p">).</span><span class="nf">to</span> <span class="n">match</span><span class="p">(</span><span class="sr">/^L(?=[0-9]{8}$)0*</span><span class="si">#{</span><span class="no">Regexp</span><span class="p">.</span><span class="nf">escape</span><span class="p">(</span><span class="n">my_model</span><span class="p">.</span><span class="nf">id</span><span class="p">.</span><span class="nf">to_s</span><span class="p">)</span><span class="si">}</span><span class="sr">$/</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="olist lowerroman">
<ol class="lowerroman" type="i">
<li>
<p>and create the corresponding method like so:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre># app/models/my_model.rb
def serial_number
  &#34;L#{id.to_s.rjust(8, &#34;0&#34;)}&#34;
end</pre>
</div>
</div>
<div class="paragraph">
<p>Don’t you just love regular expressions?</p>
</div>
]]></content:encoded></item><item><title>A Leap Into the Unknown</title><link>/post/2020-05-19-a-leap-into-the-unknown/</link><pubDate>Tue, 19 May 2020 00:00:00 +0000</pubDate><guid>/post/2020-05-19-a-leap-into-the-unknown/</guid><description>After 19 years as a teacher, I finally made the leap into a new career as a software developer. Here are my thoughts on this transition, the challenges ahead, and the feelings that come with stepping into the unknown.</description><content:encoded><![CDATA[<div class="imageblock">
<div class="content">
<img src="/assets/img/leap-of-faith.jpg" alt="leap of faith"/>
</div>
</div>
<div class="paragraph">
<p>As I mentioned in a previous post, I’ve started the journey of a career change.
After two failed attempts, I’ve finally landed a job as a fullstack developer at a really great company in the Poitiers area.
The leave request was sent out yesterday, all my colleagues are now aware, and I find myself facing a new challenge in the form of a fresh start. And honestly — it’s pretty unsettling right now.</p>
</div>
<div class="paragraph">
<p>I’m caught between two worlds: I’m over 40, I know myself well, I have extensive professional experience and recognized skills — and yet, here I am, a beginner again, stepping into a completely different world from the one I knew in public education.Of course, I knew there were big differences between the public and private sectors before making the leap, but those differences hit differently when you’re signing at the bottom of an employment contract. This time, those differences feel personal.
I’m used to being accountable to students — now I’ll be accountable to a boss.
I’m used to working my own way — now I’ll need to fit into a company workflow.
I’m used to setting my own pace — now I’ll need to meet deadlines.
I’ll ultimately have less control over my days than I used to, even if a developer’s job still allows a good deal of autonomy.
But beyond the actual work I’ll be doing day-to-day, what really stands out is how this shift might shake up my entire routine — and its impact on my family, our lifestyle, and habits we’ve built over the last 20 years. That’s the real unknown.
I’m not afraid of this new future at all. Sure, there’s a little knot in my stomach now and then, but it’s more like stage fright — the nervous energy that comes with excitement — than anxiety rooted in regret. I wanted this change. I made it happen.
Now it’s time to take that step — not into the void, but into the unknown.
So good.</p>
</div>
]]></content:encoded></item><item><title>Prevent Devise from sending confirmation emails</title><link>/post/2020-01-26-prevent-devise-from-sending-confirmation-emails/</link><pubDate>Sun, 26 Jan 2020 00:00:00 +0000</pubDate><guid>/post/2020-01-26-prevent-devise-from-sending-confirmation-emails/</guid><description>When you need to import users into a new Rails app using Devise, you may want to skip sending confirmation emails. Here’s how to do it by overriding an instance method in the User model.</description><content:encoded><![CDATA[<div class="imageblock">
<div class="content">
<img src="/assets/img/message-in-a-bottle.jpg" alt="Image par Antonios Ntoumas de Pixabay"/>
</div>
</div>
<div class="paragraph">
<p>I am about to release a huge rails project I have been working on for quite a
while. This project is meant to replace an old app that manages a database of
customers with emails. The new rails project uses Devise for authentication.</p>
</div>
<div class="paragraph">
<p>Before we push the final update to the production server, we need to find a way to import the users without sending confirmation emails, but still keep them unconfirmed until they actually login to the new system and provide a password. New registrations should also still be confirmable.</p>
</div>
<div class="paragraph">
<p>So I had to find a way to override an instance method. This is easy to do with a
simple module:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="c1"># Module to be included in a User instance whenever we don&#39;t want to send an email</span>
<span class="c1"># Useful when importing users or for testing</span>
<span class="k">module</span> <span class="nn">SkipConfirmationEmail</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">included</span><span class="p">(</span><span class="n">base</span><span class="p">)</span>
    <span class="n">base</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="ss">:include</span><span class="p">,</span> <span class="no">InstanceMethods</span><span class="p">)</span>

    <span class="n">base</span><span class="p">.</span><span class="nf">class_eval</span> <span class="k">do</span>
      <span class="kp">alias_method</span> <span class="ss">:send_confirmation_instructions</span><span class="p">,</span> <span class="ss">:return_false</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="c1"># Instance methods</span>
  <span class="k">module</span> <span class="nn">InstanceMethods</span>
    <span class="k">def</span> <span class="nf">return_false</span>
      <span class="kp">false</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>What this basically does is alias the <code>send_confirmation_instructions</code> method to
a new <code>return_false</code> instance method we can inject into our model like this :</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span>
<span class="n">user</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="ss">:include</span><span class="p">,</span> <span class="no">SkipConfirmationEmail</span><span class="p">)</span>

<span class="n">user</span><span class="p">.</span><span class="nf">send_confirmation_instructions</span>
<span class="c1">#&gt; false</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Therefore, my users will still have to confirm their account at first login on
the new system, but won’t be notified when I import the database a few days
before launch.</p>
</div>
<div class="paragraph">
<p>This can easily be adapted to serveral use cases, whenever you need to override
an instance method in an exisiting Ruby class, as seen <a href="https://gist.github.com/bluefuton/5851093">in this GitHub gist</a>.</p>
</div>
]]></content:encoded></item><item><title>Genesis of a Career Change</title><link>/post/2020-01-20-genesis-of-a-career-change/</link><pubDate>Mon, 20 Jan 2020 00:00:00 +0000</pubDate><guid>/post/2020-01-20-genesis-of-a-career-change/</guid><description>After 19 years as a teacher, I decided to change careers and become a software developer. Here are the reasons that led me to this decision, and the first lessons I’ve learned from this transformation.</description><content:encoded><![CDATA[<div class="imageblock">
<div class="content">
<img src="/assets/img/change.jpg" alt="change"/>
</div>
</div>
<div class="paragraph">
<p><em>&#34;It’s about time!&#34;</em>, my friends will probably say.</p>
</div>
<div class="paragraph">
<p>If I stayed a teacher in the French National Education system for so long, it’s because I never really listened to them.</p>
</div>
<div class="paragraph">
<p>Since the goal of this post isn’t to share my 19 years of fascinating wanderings, I’ll just say that I chose teaching more out of pragmatism than passion. That, in itself, was a mistake.</p>
</div>
<div class="paragraph">
<p>So the time has come to leave teaching and turn to full-time software development. For now, nothing is set. I’m still in my job, grading papers, running classes…​ My colleagues, my superiors, and my students know nothing about my decision. Their time will come.</p>
</div>
<div class="paragraph">
<p>I intend to come here regularly to reflect, to share my thoughts on this transformation—​both to help me formalize my own evolution and to give a hand to others who may be in a similar situation.</p>
</div>
<div class="paragraph">
<p>Here is the very first interim assessment of my process, in the form of observations that are also a bit of advice:</p>
</div>
<div class="sect1">
<h2 id="1-i-gave-myself-time"><a class="anchor" href="#1-i-gave-myself-time"></a>1. I gave myself time</h2>
<div class="sectionbody">
<div class="paragraph">
<p>This isn’t the first time I’ve had the urge to leave. In fact, less than a year ago, I had talked to colleagues about this need to go and started a few aborted steps.</p>
</div>
<div class="paragraph">
<p>My loved ones know that I’ve been thinking for years about changing paths. But deep down, I wasn’t ready. To those who say I waited too long, I would reply that I couldn’t leave without first resolving certain deeper and more intimate things.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="2-im-at-peace-with-myself"><a class="anchor" href="#2-im-at-peace-with-myself"></a>2. I’m at peace with myself</h2>
<div class="sectionbody">
<div class="paragraph">
<p>This time, my departure is firm, because I know I feel ready.</p>
</div>
<div class="paragraph">
<p>I’m not slamming the door on the National Education system. I thank it for everything it gave me, but I know it has nothing more to offer. Continuing to teach would end up wearing me down and turning me into a bitter teacher—​something I’ve always run from.</p>
</div>
<div class="paragraph">
<p>The steps ahead don’t scare me, because I feel ready.</p>
</div>
<div class="paragraph">
<p>Of course, there are still some big question marks, and they can cause stress. But fear? Not at all.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="3-i-have-a-plan-that-will-succeed"><a class="anchor" href="#3-i-have-a-plan-that-will-succeed"></a>3. I have a plan that will succeed</h2>
<div class="sectionbody">
<div class="paragraph">
<p>I don’t know if this plan will unfold the way I imagine, or if it will take longer than I expect, but in my mind and in my actions, my project is set to succeed.</p>
</div>
<div class="paragraph">
<p>I don’t wonder <em>if</em> my project will succeed, I <em>know</em> it will.</p>
</div>
<div class="paragraph">
<p>Being in harmony with myself strengthens this deep conviction.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="4-i-have-no-regrets-about-leaving-my-old-career"><a class="anchor" href="#4-i-have-no-regrets-about-leaving-my-old-career"></a>4. I have no regrets about leaving my old career</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Unlike other times in my career when I considered leaving, I have now definitively let go of the teaching profession.</p>
</div>
<div class="paragraph">
<p>Obviously, I won’t miss all the negative aspects (and there are many!), but so many great things happen in a classroom, or even among colleagues…​ Those things are harder to leave behind.</p>
</div>
<div class="paragraph">
<p>Yet my new destination makes me forget those good things. I’m looking ahead!</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="5-i-think-about-my-well-being"><a class="anchor" href="#5-i-think-about-my-well-being"></a>5. I think about my well-being</h2>
<div class="sectionbody">
<div class="paragraph">
<p>In the past, I used to tell myself that the career’s stability and flexible hours suited me, and that my lack of fulfillment was secondary.</p>
</div>
<div class="paragraph">
<p>Wanting to provide for your family is a noble goal, but what kids want a dad who’s uncomfortable in his own skin, always searching, working late into the night to find meaning…​</p>
</div>
<div class="paragraph">
<p>I’d rather offer them a fulfilled father.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="in-conclusion"><a class="anchor" href="#in-conclusion"></a>In conclusion</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Over the years, my discomfort at work became like second nature. It turned into a form of depression that quietly followed me, growing ever larger, day after day, term after term.</p>
</div>
<div class="paragraph">
<p>By finally recognizing this mental state, I was able to overcome it and stop watching my life go by without reacting.</p>
</div>
<div class="paragraph">
<p>If you’re thinking about a career change, I encourage you not to focus only on salaries or do a basic skills assessment. I encourage you to dig into the reasons that led you to choose (or not) your current profession and to seek support so you can find real peace amid the storm of change.</p>
</div>
<div class="paragraph">
<p>Feel free to comment if this message speaks to you.</p>
</div>
</div>
</div>
]]></content:encoded></item><item><title>First, I drink the coffee...</title><link>/post/2019-01-24-first-i-drink-the-coffee/</link><pubDate>Thu, 24 Jan 2019 00:00:00 +0000</pubDate><guid>/post/2019-01-24-first-i-drink-the-coffee/</guid><description>Here is a wallpaper I made. Feel free to download…​</description><content:encoded><![CDATA[<div class="paragraph">
<p>…​Then, I do the things.</p>
</div>
<div class="paragraph">
<p>Here is a wallpaper I made. Feel free to download…​</p>
</div>
<div class="paragraph">
<p><a href="/assets/wallpapers/first-i-drink-the-coffee.jpg">Here it is in 1080x1920 resolution</a></p>
</div>
]]></content:encoded></item><item><title/><link>/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>/about/</guid><description>&lt;div class="imageblock left"&gt;
&lt;div class="content"&gt;
&lt;img src="/assets/img/me3.jpg" alt="My Photo" width="150" height="150"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;My name is Cédric Delalande. I am a french software engineer. I live near Poitiers, France. I am a husband and a
father of four.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After a 19-year career as an English teacher, I switched to IT - no regrets. Check out my &lt;a href="../resume/"&gt;résumé&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I code in Ruby on Rails. I’m interested in Rust, Golang, Crystal and Gleam. I have a DevOps streak, with solid
experience in Kubernetes on Google Cloud and AWS.&lt;/p&gt;
&lt;/div&gt;</description><content:encoded><![CDATA[<div class="imageblock left">
<div class="content">
<img src="/assets/img/me3.jpg" alt="My Photo" width="150" height="150"/>
</div>
</div>
<div class="paragraph">
<p>My name is Cédric Delalande. I am a french software engineer. I live near Poitiers, France. I am a husband and a
father of four.</p>
</div>
<div class="paragraph">
<p>After a 19-year career as an English teacher, I switched to IT - no regrets. Check out my <a href="../resume/">résumé</a>.</p>
</div>
<div class="paragraph">
<p>I code in Ruby on Rails. I’m interested in Rust, Golang, Crystal and Gleam. I have a DevOps streak, with solid
experience in Kubernetes on Google Cloud and AWS.</p>
</div>
<div class="paragraph">
<p>I have ADHD — without the H — which means I’m a bit of a scatterbrain. I have trouble switching context, but I’m also
very creative and I can hyperfocus on a task for hours, days, weeks, months, as long as I manage to eat, sleep, go to
the bathroom and take breaks, which sounds a lot easier than it is. Believe me.</p>
</div>
<div class="paragraph">
<p>I maintain the <a href="https://github.com/MoskitoHero/barley">Barley gem</a>, a ruby ActiveModel serializer, although I don’t have
much time to devote to it these days.</p>
</div>
<div class="paragraph">
<p>I have a soft spot for clean code and software architecture in general. I’m a Doom Emacs user, but constantly kicking
the tyres of alternatives, like Zed, for instance. I type in Ergo‑L.</p>
</div>
<div class="paragraph">
<p>You can find me on the web under my pseudonym, MoskitoHero, or under my real name.</p>
</div>
]]></content:encoded></item><item><title/><link>/resume/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>/resume/</guid><description>&lt;div class="cv-container"&gt;
&lt;div class="resume-header"&gt;
&lt;div class="header-left"&gt;
&lt;div class="name-block"&gt;
&lt;span class="first-name"&gt;CÉDRIC&lt;/span&gt;
&lt;span class="last-name"&gt;DELALANDE&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="header-right"&gt;
&lt;div class="contact-column"&gt;
&lt;p&gt;&lt;i class="fa fa-map-marker"&gt;&lt;/i&gt; Poitiers, France&lt;/p&gt;
&lt;p&gt;&lt;i class="fa fa-phone"&gt;&lt;/i&gt; &lt;a href="#" id="phone-link"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;i class="fa fa-envelope"&gt;&lt;/i&gt; &lt;a href="#" id="email-link"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="contact-column"&gt;
&lt;p&gt;&lt;i class="fa fa-globe"&gt;&lt;/i&gt; &lt;a href="https://moskitohero.com"&gt;moskitohero.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;i class="fa-brands fa-github"&gt;&lt;/i&gt; &lt;a href="https://github.com/MoskitoHero"&gt;github.com/MoskitoHero&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;i class="fa-brands fa-mastodon"&gt;&lt;/i&gt; &lt;a href="https://ruby.social/@MoskitoHero"&gt;ruby.social/@MoskitoHero&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="pdf-download"&gt;
&lt;a href="/assets/pdf/cedric_delalande.pdf"&gt;
&lt;i class="fa-solid fa-file-pdf"&gt;&lt;/i&gt; Download PDF version
&lt;/a&gt;
&lt;/div&gt;
&lt;h2 class="section-title"&gt;Who am I?&lt;/h2&gt;
&lt;div class="who-am-i-section"&gt;
&lt;div class="photo-container"&gt;
&lt;img src="/assets/img/cedric-photo.jpg" alt="Cédric Delalande"&gt;
&lt;/div&gt;
&lt;div class="intro-text"&gt;
&lt;p&gt;Senior Ruby Developer with 15+ years experience building scalable web applications. Expert in team leadership, system architecture, and cloud migrations.&lt;/p&gt;
&lt;p&gt;Creative, flexible and eclectic.&lt;/p&gt;
&lt;p&gt;I believe clean, tested code is key to maintainability and performance. I believe in trust and ownership.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="skills-bars"&gt;
&lt;div class="skill-bar"&gt;
&lt;span class="skill-label"&gt;Ruby on Rails&lt;/span&gt;
&lt;div class="bar-container"&gt;
&lt;div class="bar-fill" style="width: 100%;"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="skill-bar"&gt;
&lt;span class="skill-label"&gt;Kubernetes&lt;/span&gt;
&lt;div class="bar-container"&gt;
&lt;div class="bar-fill" style="width: 90%;"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="skill-bar"&gt;
&lt;span class="skill-label"&gt;Team Leadership&lt;/span&gt;
&lt;div class="bar-container"&gt;
&lt;div class="bar-fill" style="width: 90%;"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="skill-bar"&gt;
&lt;span class="skill-label"&gt;AWS/GCP&lt;/span&gt;
&lt;div class="bar-container"&gt;
&lt;div class="bar-fill" style="width: 80%;"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="skill-bar"&gt;
&lt;span class="skill-label"&gt;CI/CD&lt;/span&gt;
&lt;div class="bar-container"&gt;
&lt;div class="bar-fill" style="width: 80%;"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="skill-bar"&gt;
&lt;span class="skill-label"&gt;Rust&lt;/span&gt;
&lt;div class="bar-container"&gt;
&lt;div class="bar-fill" style="width: 40%;"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 class="section-title"&gt;Experience&lt;/h2&gt;
&lt;div class="experience-list"&gt;
&lt;div class="experience-entry"&gt;
&lt;div class="entry-date"&gt;2025-Present&lt;/div&gt;
&lt;div class="entry-content"&gt;
&lt;h4&gt;Full Stack Developer&lt;/h4&gt;
&lt;div class="entry-company"&gt;Happydemics - Full Remote&lt;/div&gt;
&lt;p&gt;Full stack development for market research platform. Ruby on Rails backend development. React/TypeScript frontend development. Python development. AI prompt engineering and integration.&lt;/p&gt;</description><content:encoded><![CDATA[
<div class="cv-container">

<div class="resume-header">
  <div class="header-left">
    <div class="name-block">
      <span class="first-name">CÉDRIC</span>
      <span class="last-name">DELALANDE</span>
    </div>
  </div>

  <div class="header-right">
    <div class="contact-column">
      <p><i class="fa fa-map-marker"></i> Poitiers, France</p>
      <p><i class="fa fa-phone"></i> <a href="#" id="phone-link"></a></p>
      <p><i class="fa fa-envelope"></i> <a href="#" id="email-link"></a></p>
    </div>
    <div class="contact-column">
      <p><i class="fa fa-globe"></i> <a href="https://moskitohero.com">moskitohero.com</a></p>
      <p><i class="fa-brands fa-github"></i> <a href="https://github.com/MoskitoHero">github.com/MoskitoHero</a></p>
      <p><i class="fa-brands fa-mastodon"></i> <a href="https://ruby.social/@MoskitoHero">ruby.social/@MoskitoHero</a></p>
    </div>
  </div>
</div>

<div class="pdf-download">
  <a href="/assets/pdf/cedric_delalande.pdf">
    <i class="fa-solid fa-file-pdf"></i> Download PDF version
  </a>
</div>

<h2 class="section-title">Who am I?</h2>

<div class="who-am-i-section">
  <div class="photo-container">
    <img src="/assets/img/cedric-photo.jpg" alt="Cédric Delalande">
  </div>

  <div class="intro-text">
    <p>Senior Ruby Developer with 15+ years experience building scalable web applications. Expert in team leadership, system architecture, and cloud migrations.</p>

    <p>Creative, flexible and eclectic.</p>

    <p>I believe clean, tested code is key to maintainability and performance. I believe in trust and ownership.</p>
  </div>

  <div class="skills-bars">
    <div class="skill-bar">
      <span class="skill-label">Ruby on Rails</span>
      <div class="bar-container">
        <div class="bar-fill" style="width: 100%;"></div>
      </div>
    </div>
    <div class="skill-bar">
      <span class="skill-label">Kubernetes</span>
      <div class="bar-container">
        <div class="bar-fill" style="width: 90%;"></div>
      </div>
    </div>
    <div class="skill-bar">
      <span class="skill-label">Team Leadership</span>
      <div class="bar-container">
        <div class="bar-fill" style="width: 90%;"></div>
      </div>
    </div>
    <div class="skill-bar">
      <span class="skill-label">AWS/GCP</span>
      <div class="bar-container">
        <div class="bar-fill" style="width: 80%;"></div>
      </div>
    </div>
    <div class="skill-bar">
      <span class="skill-label">CI/CD</span>
      <div class="bar-container">
        <div class="bar-fill" style="width: 80%;"></div>
      </div>
    </div>
    <div class="skill-bar">
      <span class="skill-label">Rust</span>
      <div class="bar-container">
        <div class="bar-fill" style="width: 40%;"></div>
      </div>
    </div>
  </div>
</div>

<h2 class="section-title">Experience</h2>

<div class="experience-list">
  <div class="experience-entry">
    <div class="entry-date">2025-Present</div>
    <div class="entry-content">
      <h4>Full Stack Developer</h4>
      <div class="entry-company">Happydemics - Full Remote</div>
      <p>Full stack development for market research platform. Ruby on Rails backend development. React/TypeScript frontend development. Python development. AI prompt engineering and integration.</p>
      <div class="entry-tags">Ruby on Rails / React / TypeScript / Python / AI / Prompt Engineering</div>
    </div>
  </div>

  <div class="experience-entry">
    <div class="entry-date">2025</div>
    <div class="entry-content">
      <h4>Senior Backend Developer</h4>
      <div class="entry-company">Perfect Memory - Full Remote</div>
      <p>Ruby development for enterprise applications. Backend system optimization and maintenance. RDF Knowledge Graphs implementation. Zed IDE language extension development in Rust.</p>
      <div class="entry-tags">Ruby / Backend Architecture / Knowledge Graphs / Rust / IDE Development</div>
    </div>
  </div>

  <div class="experience-entry">
    <div class="entry-date">2021-Present</div>
    <div class="entry-content">
      <h4>Open-source maintainer</h4>
      <div class="entry-company"></div>
      <p>Creator of the <a href="https://github.com/MoskitoHero/barley"><em>barley</em> gem</a>, a fast Ruby model serializer. Maintainer of the <a href="https://github.com/svenfuchs/rails-i18n"><em>rails-i18n</em> gem</a>.</p>
      <div class="entry-tags">Open Source / Performance Optimization / Community Leadership</div>
    </div>
  </div>

  <div class="experience-entry">
    <div class="entry-date">2021-2024</div>
    <div class="entry-content">
      <h4>Lead Backend Developer</h4>
      <div class="entry-company">StockPro - Full Remote</div>
      <p>Backend squad management (5 developers). Junior mentoring. Architectural decisions. Test suite upgrade (20 → 85% coverage). Technological debt management. Performance improvements. Heroku to AWS migration. Third-party API integration. Sorbet integration.</p>
      <div class="entry-tags">Ruby / Team Leadership / AWS Migration / Sorbet / Stripe / RSpec</div>
    </div>
  </div>

  <div class="experience-entry">
    <div class="entry-date">2020 - 2021</div>
    <div class="entry-content">
      <h4>Full Stack Developer</h4>
      <div class="entry-company">Domalys</div>
      <p>Ruby on Rails and Go development. Test suite creation (0 → 60% coverage). Heroku to Kubernetes (GKE) migration for international launch. Application and IoT monitoring. Process, data flow and technological standardisation. Cloud architecture, VPC and security. CI/CD.</p>
      <div class="entry-tags">Rails / Kubernetes / Cloud Migration / Terraform / GraphQL / Golang</div>
    </div>
  </div>

  <div class="experience-entry">
    <div class="entry-date">2007 - 2020</div>
    <div class="entry-content">
      <h4>Freelance Full Stack Developer</h4>
      <div class="entry-company">MoskitoHero - Self-employed</div>
      <p>Ruby on Rails & PHP application development. Web and press graphic design. Customer care.</p>
      <div class="entry-tags">Ruby on Rails / Client Management / Full Stack Development</div>
    </div>
  </div>
</div>

<h2 class="section-title">Education</h2>

<div class="education-list">
  <div class="education-entry">
    <div class="entry-date">2023</div>
    <div class="entry-content">
      <h4>Google Cloud Professional Cloud Security Engineer</h4>
      <div class="entry-company">Google Cloud</div>
    </div>
  </div>

  <div class="education-entry">
    <div class="entry-date">2022</div>
    <div class="entry-content">
      <h4>Certified Kubernetes Administrator (CKA)</h4>
      <div class="entry-company">Cloud Native Computing Foundation</div>
    </div>
  </div>

  <div class="education-entry">
    <div class="entry-date">1999-2001</div>
    <div class="entry-content">
      <h4>English Language and Civilisation Master</h4>
      <div class="entry-company">Université de Poitiers</div>
    </div>
  </div>
</div>

<div class="bottom-section">
  <div class="bottom-column">
    <h3 class="mini-section-title">Spoken Languages</h3>
    <p><strong>French</strong> - native<br>
    <strong>English</strong> - fluent</p>
  </div>

  <div class="bottom-column">
    <h3 class="mini-section-title">Key Achievements</h3>
    <ul>
      <li>15+ years Ruby expertise</li>
      <li>Led team of 5 developers</li>
      <li>Open source contributor</li>
      <li>Multi-language developer</li>
    </ul>
  </div>

  <div class="bottom-column">
    <h3 class="mini-section-title">Interests</h3>
    <p>System architecture, performance optimization, language design, team mentoring</p>
  </div>
</div>

</div><!-- end cv-container -->

<style>
.cv-container {
  max-width: 900px;
  margin: 0 auto;
  font-family: 'Open Sans', Helvetica, sans-serif;
}

.cv-container a {
  color: #bf616a;
  text-decoration: none;
}

.cv-container a:hover,
.cv-container a:active,
.cv-container a:visited {
  color: #bf616a;
  text-decoration: none;
}

/* Header Section */
.resume-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 1.5rem;
  gap: 2rem;
  flex-wrap: wrap;
}

.header-left {
  flex: 1;
  min-width: 300px;
}

.name-block {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-bottom: 0.5rem;
}

.first-name, .last-name {
  padding: 0.3rem 0;
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  display: inline-block;
  width: fit-content;
  font-family: 'Palanquin Dark', Helvetica, sans-serif;
}

.job-title {
  font-size: 1.5rem;
  font-weight: 300;
  margin: 1rem 0 0 0;
  color: #d8dee9;
  font-family: 'Palanquin', Helvetica, sans-serif;
}

.header-right {
  display: flex;
  gap: 2rem;
  font-size: 0.9rem;
}

.contact-column {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.contact-column p {
  margin: 0;
  line-height: 1.6;
}

.contact-column i {
  width: 20px;
  text-align: center;
  margin-right: 0.3rem;
}

.contact-column a {
  color: #bf616a;
  text-decoration: none;
}

.contact-column a:hover {
  color: #bf616a;
  text-decoration: none;
}

/* PDF Download */
.pdf-download {
  text-align: center;
  margin: 1.5rem 0;
  padding: 1rem;
  background-color: #bf616a23;
  border: 1px solid #bf616a;
  border-radius: 6px;
}

.pdf-download a {
  color: #bf616a;
  text-decoration: none;
  font-weight: 600;
  font-size: 1rem;
}

.pdf-download a:hover {
  color: #bf616a;
}

.pdf-download i {
  margin-right: 0.5rem;
  font-size: 1.2rem;
}

/* Section Titles */
.section-title {
  font-family: 'Palanquin', Helvetica, sans-serif;
  padding-top: 1em;
  line-height: 1.2em;
  margin-bottom: 1rem;
  font-size: 1.8rem;
}

.mini-section-title {
  font-family: 'Palanquin', Helvetica, sans-serif;
  padding-top: 0.5em;
  line-height: 1.2em;
  margin-bottom: 0.5rem;
  font-size: 1.2rem;
}

/* Who Am I Section */
.who-am-i-section {
  display: grid;
  grid-template-columns: 200px 1fr 1fr;
  gap: 2rem;
  margin-bottom: 2rem;
  align-items: start;
}

.photo-container {
  width: 100%;
}

.photo-container img {
  width: 100%;
  height: auto;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.intro-text {
  font-size: 0.95rem;
  line-height: 1.6;
}

.intro-text p {
  margin-bottom: 0.8rem;
}

/* Skills Bars */
.skills-bars {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.skill-bar {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.skill-label {
  padding: 0.2rem 0;
  font-size: 0.8rem;
  font-weight: 600;
  display: inline-block;
  width: fit-content;
  font-family: 'Palanquin', Helvetica, sans-serif;
}

.bar-container {
  width: 100%;
  height: 8px;
  background-color: #434c5e;
  border-radius: 2px;
  overflow: hidden;
}

.bar-fill {
  height: 100%;
  background: #9AB3A6;
  transition: width 0.3s ease;
}

/* Experience & Education Entries */
.experience-list, .education-list {
  margin-bottom: 2rem;
}

.experience-entry, .education-entry {
  display: grid;
  grid-template-columns: 120px 1fr;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}

.entry-date {
  font-size: 0.9rem;
  color: #9fadc9;
  font-weight: 600;
  padding-top: 0.2rem;
}

.entry-content h4 {
  margin: 0 0 0.3rem 0;
  font-size: 1.1rem;
  font-weight: 700;
  font-family: 'Palanquin', Helvetica, sans-serif;
  padding-top: 0;
  line-height: 1.2em;
}

.entry-company {
  font-size: 0.85rem;
  font-weight: 600;
  color: #9fadc9;
  margin-bottom: 0.5rem;
}

.entry-content p {
  margin: 0.5rem 0;
  line-height: 1.6;
  font-size: 0.9rem;
}

.entry-tags {
  font-size: 0.8rem;
  color: #9fadc9;
  font-style: italic;
  margin-top: 0.5rem;
}

.entry-content a {
  color: #bf616a;
  text-decoration: none;
}

.entry-content a:hover {
  color: #bf616a;
}

/* Bottom Section */
.bottom-section {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2rem;
  margin: 2rem 0;
}

.bottom-column {
  font-size: 0.9rem;
}

.bottom-column p {
  line-height: 1.6;
  margin: 0.5rem 0;
}

.bottom-column ul {
  list-style: none;
  padding: 0;
  margin: 0.5rem 0;
}

.bottom-column ul li {
  padding: 0.2rem 0;
  position: relative;
  padding-left: 1rem;
}

.bottom-column ul li:before {
  content: "•";
  position: absolute;
  left: 0;
  font-weight: bold;
}

/* Responsive Design */
@media (max-width: 900px) {
  .who-am-i-section {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }

  .photo-container {
    max-width: 200px;
    margin: 0 auto;
  }

  .skills-bars {
    max-width: 500px;
    margin: 0 auto;
  }
}

@media (max-width: 768px) {
  .resume-header {
    flex-direction: column;
  }

  .header-left {
    width: 100%;
  }

  .header-right {
    flex-direction: column;
    gap: 1rem;
    width: 100%;
  }

  .experience-entry, .education-entry {
    grid-template-columns: 1fr;
    gap: 0.5rem;
  }

  .entry-date {
    font-style: italic;
  }

  .bottom-section {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }
}

@media print {
  .pdf-download {
    display: none;
  }
}
</style>

<script>
(function() {
  // Obfuscated contact information
  var phone = atob('KzMzIDYgMjUgMjIgMDkgMjA=');
  var email = atob('Y2VkcmljQGRlbGFsYW5kZS5tZQ==');

  // Set phone number
  var phoneEl = document.getElementById('phone-link');
  if (phoneEl) {
    phoneEl.textContent = phone;
    phoneEl.href = 'tel:' + phone.replace(/\s/g, '');
  }

  // Set email
  var emailEl = document.getElementById('email-link');
  if (emailEl) {
    emailEl.textContent = email;
    emailEl.href = 'mailto:' + email;
  }
})();
</script>
]]></content:encoded></item></channel></rss>