<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://www.yellowduck.be/pretty-atom-feed-v3.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <link href="https://www.yellowduck.be" rel="alternate"/>
  <link href="https://www.yellowduck.be/posts/feed" rel="self"/>
  <author>
    <name>Pieter Claerhout</name>
    <email>pieter@yellowduck.be</email>
  </author>
  <id>https://www.yellowduck.be/posts/feed</id>
  <title>🐥 YellowDuck.be</title>
  <updated>2026-05-12T17:00:00Z</updated>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/thinking-elixir-podcast-303-the-taming-of-the-slop" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;EEF 2026 election candidates are out, Elixir-Vibe launches tools to fight AI code slop, erlang_python 3.0.0 embeds CPython into the BEAM, ElixirConf EU 2026 videos are dropping, and more!&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://podcast.thinkingelixir.com/303&quot;&gt;Continue reading on &lt;strong&gt;podcast.thinkingelixir.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-12T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/thinking-elixir-podcast-303-the-taming-of-the-slop</id>
    <title>🔗 Thinking Elixir Podcast 303: The Taming of the Slop</title>
    <updated>2026-05-12T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/intelligent-curation-tagging-for-creative-workflows" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;I struggled with organizing 14,000 photos and wanted an AI to help identify the ones matching my aesthetic. Initially, I relied on a vision model to classify them based on a prose description, but the results were inconsistent and resource-intensive, spiking RAM usage to 15GB. The new architecture focuses on learning from my feedback instead of static prompts. Using CLIP embeddings and a preference model, it tracks my ratings to improve selection. The ingestion process involves measuring technical image qualities and extracting metadata before sending photos to the AI worker. This staged pipeline lets each component fail and retry independently, providing a smooth curation experience. After running just a few sessions, I managed to surface 214 meaningful images out of 3,600 processed. The entire backlog could be managed within a week at this rate!&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://qwelian.com/posts/FINE_SHYT_Intelligent_Curation_Tagging_for_Creative_Workflows&quot;&gt;Continue reading on &lt;strong&gt;qwelian.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/ai&quot;&gt;#ai&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-12T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/intelligent-curation-tagging-for-creative-workflows</id>
    <title>🔗 Intelligent curation tagging for creative workflows</title>
    <updated>2026-05-12T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/common-cors-errors-and-how-to-fix-them" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Cross-Origin Resource Sharing (CORS) provides browser security by managing requests across different origins. CORS errors arise when the server does not send back the expected headers, leading to blocked requests in the browser&apos;s console.&lt;/p&gt;
&lt;p&gt;This article outlines common CORS errors, including missing headers and preflight issues, and offers detailed solutions. Developers are advised to configure their server settings correctly to allow cross-origin interactions while maintaining secure API access.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://workos.com/blog/common-cors-errors-and-how-to-fix-them&quot;&gt;Continue reading on &lt;strong&gt;workos.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/frontend&quot;&gt;#frontend&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/http&quot;&gt;#http&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-12T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/common-cors-errors-and-how-to-fix-them</id>
    <title>🔗 Common CORS errors and how to fix them</title>
    <updated>2026-05-12T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/restarting-supervisord-daemons-by-working-directory-on-linux" rel="alternate"/>
    <content type="html">&lt;p&gt;When you run multiple instances of the same Laravel Horizon worker (or any supervisord-managed process) across different deployment directories, you&apos;ll quickly notice that supervisord assigns generic auto-generated names like &lt;code&gt;daemon-282661:daemon-282661_00&lt;/code&gt;. There&apos;s no obvious way to tell which daemon belongs to which application directory — until you know the trick.&lt;/p&gt;
&lt;h1&gt;Finding the working directory of a process&lt;/h1&gt;
&lt;p&gt;On Linux, every running process exposes its current working directory as a symlink under &lt;code&gt;/proc&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;readlink&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/proc/&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;string-special-path&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;string-special-path&quot;&gt;/cwd&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To check all your Horizon workers at once, grab their PIDs from &lt;code&gt;supervisorctl status&lt;/code&gt; and loop over them:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-repeat&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt; &lt;span class=&quot;keyword-conditional&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;91286&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;91287&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;91288&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;91290&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;keyword-repeat&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;function-builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;readlink&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/proc/&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;variable-parameter&quot;&gt;/cwd&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;keyword-repeat&quot;&gt;done&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Restarting daemons for a specific directory&lt;/h1&gt;
&lt;p&gt;Once you can map PIDs to directories, it&apos;s straightforward to build a script that restarts only the daemons running from a given path:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-directive&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;constant&quot;&gt;TARGET_DIR&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;:?&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;Usage:&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;string&quot;&gt; &lt;working-directory&gt;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;constant&quot;&gt;TARGET_DIR&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;realpath&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;TARGET_DIR&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;supervisorctl&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;keyword-repeat&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;function-builtin&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;keyword-repeat&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span class=&quot;function-builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;RUNNING&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;function-builtin&quot;&gt;continue&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;function-builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;&amp;lbrace;print $1&amp;rbrace;&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;function-builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-oP&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;pid \K[0-9]+&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-z&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span class=&quot;function-builtin&quot;&gt;continue&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;cwd&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;readlink&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;/proc/&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/cwd&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;string-special-path&quot;&gt;/dev/null&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span class=&quot;keyword-conditional&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;cwd&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;TARGET_DIR&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;keyword-conditional&quot;&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;        &lt;span class=&quot;function-builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;Restarting &lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;string&quot;&gt; (pid &lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;, dir &lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;cwd&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;)&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;        &lt;span class=&quot;function-call&quot;&gt;supervisorctl&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;restart&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span class=&quot;keyword-conditional&quot;&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;&lt;span class=&quot;keyword-repeat&quot;&gt;done&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;How it works&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;supervisorctl status&lt;/code&gt; lists all daemons with their current PIDs.&lt;/li&gt;
&lt;li&gt;Non-&lt;code&gt;RUNNING&lt;/code&gt; daemons are skipped (they have no PID to look up).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/proc/&lt;pid&gt;/cwd&lt;/code&gt; resolves the actual working directory for each process.&lt;/li&gt;
&lt;li&gt;Any daemon whose working directory matches the target path (or is a subpath of the target path) gets restarted.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Usage&lt;/h1&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;chmod&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;+x&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;restart-daemons.sh&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;./restart-daemons.sh&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/home/forge/my-app/current&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is especially useful on servers running multiple deployments of the same application — for example, a staging environment with both a &lt;code&gt;current&lt;/code&gt; and a &lt;code&gt;previous&lt;/code&gt; release still running workers.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/laravel&quot;&gt;#laravel&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/terminal&quot;&gt;#terminal&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/linux&quot;&gt;#linux&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/sysadmin&quot;&gt;#sysadmin&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-11T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/restarting-supervisord-daemons-by-working-directory-on-linux</id>
    <title>🐥 Restarting supervisord daemons by working directory on Linux</title>
    <updated>2026-05-11T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/shell-tricks-that-actually-make-life-easier-and-save-your-sanity" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;The post is a practical roundup of shell shortcuts and habits that make command-line work faster and less frustrating. It highlights useful editing, navigation, and safety tips for Bash/Zsh users, plus a few portable tricks that work across POSIX shells as well.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://blog.hofstede.it/shell-tricks-that-actually-make-life-easier-and-save-your-sanity/&quot;&gt;Continue reading on &lt;strong&gt;blog.hofstede.it&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/terminal&quot;&gt;#terminal&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-11T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/shell-tricks-that-actually-make-life-easier-and-save-your-sanity</id>
    <title>🔗 Shell tricks that actually make life easier (and save your sanity)</title>
    <updated>2026-05-11T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/anti-frameworkism-choosing-native-web-apis-over-frameworks" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Today’s browsers can handle most issues that frontend frameworks were created to solve. Developers often still opt for frameworks like React, Angular, or Vue, which can increase page weight, hurt performance, and affect SEO despite native web APIs being sufficient for many tasks.&lt;/p&gt;
&lt;p&gt;This article identifies the disparity between &quot;frameworkism&quot; and &quot;anti-frameworkism.&quot; It emphasizes that frameworks are not always necessary and advocates for a native API-first approach where developers rely on native browser capabilities before turning to frameworks.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://blog.logrocket.com/anti-frameworkism-native-web-apis&quot;&gt;Continue reading on &lt;strong&gt;blog.logrocket.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/javascript&quot;&gt;#javascript&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/frontend&quot;&gt;#frontend&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/html&quot;&gt;#html&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-11T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/anti-frameworkism-choosing-native-web-apis-over-frameworks</id>
    <title>🔗 Anti-frameworkism: Choosing native web APIs over frameworks</title>
    <updated>2026-05-11T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/laravel-raised-money-and-now-injects-ads-directly-into-your-agent" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Did Laravel really need to change their agent documentation to push Laravel Cloud? After raising a $57M Series A, it&apos;s clear they want to monetize effectively. The latest PR suggests agents should default to Laravel Cloud for deployments, sparking complaints from users about the perceived bias towards Laravel&apos;s commercial platform. This shift raises questions about the erosion of community trust and the balance between monetization and quality support. Interestingly, many still recommend Laravel Cloud without the need for forceful promotion. However, it prompts a broader discussion about how ads within development tools might impact user experience and community sentiment. If Laravel&apos;s commercial segment is doing well, why risk alienating devoted users?&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://techstackups.com/articles/laravel-raised-money-and-now-injects-ads-directly-into-your-agent/&quot;&gt;Continue reading on &lt;strong&gt;techstackups.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/laravel&quot;&gt;#laravel&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/announcement&quot;&gt;#announcement&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-10T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/laravel-raised-money-and-now-injects-ads-directly-into-your-agent</id>
    <title>🔗 Laravel raised money and now injects ads directly into your agent</title>
    <updated>2026-05-10T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/migrating-from-digitalocean-to-hetzner-from-1-432-to-233-month-with-zero-downtime" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;I saved over $1,200 per month by migrating from DigitalOcean to Hetzner. Previously, I paid $1,432/month for a droplet, while the Hetzner AX162-R costs only $233/month. Managing 248 GB of MySQL data across 30 databases and hosting 34 Nginx sites meant careful planning to avoid downtime. I implemented a six-phase strategy that included live MySQL replication using &lt;code&gt;mydumper&lt;/code&gt; for fast data export and loading, and a well-scripted DNS cutover that reduced TTL to allow for quick switching. After the move, I noticed significant performance improvements thanks to MySQL 8.0&apos;s enhancements. The process involved meticulous configuration of services and a thorough testing phase before the final switch-over. The transition was seamless, with all traffic constantly routed during migration, solidifying the importance of planning when moving critical infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://isayeter.com/posts/digitalocean-to-hetzner-migration/&quot;&gt;Continue reading on &lt;strong&gt;isayeter.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/mysql&quot;&gt;#mysql&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/linux&quot;&gt;#linux&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-10T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/migrating-from-digitalocean-to-hetzner-from-1-432-to-233-month-with-zero-downtime</id>
    <title>🔗 Migrating from DigitalOcean to Hetzner: from $1,432 to $233/month with zero downtime</title>
    <updated>2026-05-10T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/pg-textsearch-1-0-how-we-built-a-bm25-search-engine-on-postgres-pages" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Building a BM25 search engine on Postgres required addressing significant limitations of the built-in &lt;code&gt;ts_rank&lt;/code&gt;. As I learned, the lack of inverse document frequency and term frequency saturation affected the relevance and scalability of searches. By creating &lt;code&gt;pg_textsearch&lt;/code&gt;, they implemented real BM25 scoring via a native indexing solution in C integrated with Postgres.&lt;/p&gt;
&lt;p&gt;The design included a hybrid architecture with a write-optimized memtable and immutable disk segments. Notably, they achieved fast query performance through optimizations like Block-Max WAND, allowing for much quicker retrieval of top results without scoring every match. With extensive benchmarks, they showed &lt;code&gt;pg_textsearch&lt;/code&gt; can outperform existing tools like ParadeDB, achieving up to 6.5x speed increases on 138 million document queries. This combination of efficiency and native integration makes it a compelling option for teams needing robust search capabilities within Postgres.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://www.tigerdata.com/blog/pg-textsearch-bm25-full-text-search-postgres&quot;&gt;Continue reading on &lt;strong&gt;www.tigerdata.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/postgresql&quot;&gt;#postgresql&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-10T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/pg-textsearch-1-0-how-we-built-a-bm25-search-engine-on-postgres-pages</id>
    <title>🔗 pg_textsearch: How we built a BM25 search engine on Postgres pages</title>
    <updated>2026-05-10T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/make-your-shell-scripts-environment-variables-overridable" rel="alternate"/>
    <content type="html">&lt;p&gt;When writing shell scripts, hardcoded values are a common source of friction. A script that works perfectly on your machine may need adjustments on a colleague&apos;s setup, in CI, or in production — and forcing people to edit the script itself is messy.&lt;/p&gt;
&lt;p&gt;There&apos;s a simple Bash idiom that solves this cleanly.&lt;/p&gt;
&lt;h1&gt;The Problem&lt;/h1&gt;
&lt;p&gt;Consider a typical startup script:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;8888&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;0.0.0.0&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;uvicorn&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;myapp:app&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--host&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--port&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The port is hardcoded. If you need to run two instances, or if &lt;code&gt;8888&lt;/code&gt; is already taken, you have to edit the file.&lt;/p&gt;
&lt;h1&gt;The Fix: &lt;code&gt;$&amp;lbrace;VAR:-default&amp;rbrace;&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;Replace the hardcoded assignment with a default-value expansion:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;8888&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;0.0.0.0&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;uvicorn&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;myapp:app&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--host&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--port&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The syntax &lt;code&gt;$&amp;lbrace;VAR:-default&amp;rbrace;&lt;/code&gt; means:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Use the value of &lt;code&gt;$VAR&lt;/code&gt; if it is set and non-empty; otherwise use &lt;code&gt;default&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The script&apos;s behaviour is unchanged when no environment variable is provided — the default kicks in. But now callers can override it without touching the file:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;9000&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;./bin/start.sh&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or by exporting it beforehand:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-import&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;9000&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;./bin/start.sh&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Why This Matters&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No edits required.&lt;/strong&gt; The script stays clean; overrides happen at call time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI-friendly.&lt;/strong&gt; Most CI systems let you set environment variables per job. Your script picks them up automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Self-documenting.&lt;/strong&gt; The default value lives right next to the variable name, so readers immediately know what to expect.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composable.&lt;/strong&gt; You can layer overrides: a &lt;code&gt;.env&lt;/code&gt; file, a CI variable, or a one-liner prefix — whatever fits the context.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Variants Worth Knowing&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Syntax&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$&amp;lbrace;VAR:-default&amp;rbrace;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;default&lt;/code&gt; if &lt;code&gt;VAR&lt;/code&gt; is unset &lt;strong&gt;or empty&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$&amp;lbrace;VAR-default&amp;rbrace;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;default&lt;/code&gt; only if &lt;code&gt;VAR&lt;/code&gt; is unset (empty string is kept)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$&amp;lbrace;VAR:=default&amp;rbrace;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;default&lt;/code&gt; and &lt;strong&gt;assign it back&lt;/strong&gt; to &lt;code&gt;VAR&lt;/code&gt; if unset or empty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$&amp;lbrace;VAR:?message&amp;rbrace;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Abort with &lt;code&gt;message&lt;/code&gt; if &lt;code&gt;VAR&lt;/code&gt; is unset or empty&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For most cases, &lt;code&gt;$&amp;lbrace;VAR:-default&amp;rbrace;&lt;/code&gt; is the right choice. The &lt;code&gt;:=&lt;/code&gt; variant is useful when you want the variable available for the rest of the script without repeating the default.&lt;/p&gt;
&lt;h1&gt;A Real-World Example&lt;/h1&gt;
&lt;p&gt;Here&apos;s a before/after for a uvicorn startup script:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;0.0.0.0&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;8888&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;uvicorn&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;docai.api.app:app&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--host&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--port&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;0.0.0.0&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;8888&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;uvicorn&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;docai.api.app:app&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--host&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--port&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two characters added per line (&lt;code&gt;$&amp;lbrace;&lt;/code&gt; and &lt;code&gt;:-&lt;/code&gt;), zero behaviour change by default, and now fully overridable from the outside. A small habit with outsized payoff.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/terminal&quot;&gt;#terminal&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-09T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/make-your-shell-scripts-environment-variables-overridable</id>
    <title>🐥 Make your shell scripts&apos; environment variables overridable</title>
    <updated>2026-05-09T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/tidewave-changed-how-i-feel-about-ai-assisted-coding" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;The author initially disliked AI coding assistants, finding inline autocomplete intrusive and distracting from their work. After exploring GitHub Copilot Pro agents in Zed, they realized the core problem: AI agents operate at the wrong abstraction layer, viewing static code rather than the running application.&lt;/p&gt;
&lt;p&gt;Tidewave, an Elixir-based tool by José Valim, runs directly inside applications at runtime and lets agents see what developers see. The author&apos;s workflow involves connecting Tidewave to Copilot, using Gemini 3.1 Pro to implement features while watching the agent test changes in the browser, then manually refactoring the generated code in Zed before committing.&lt;/p&gt;
&lt;p&gt;This approach resolves the abstraction layer problem and keeps the developer engaged with their codebase. The author maintains manual control over code quality and structure, avoiding the pitfalls of fully autonomous agents while still leveraging AI&apos;s capabilities effectively.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://fabrithedev.com/posts/2026/3/tidewave-is-game-changer?locale=nl&quot;&gt;Continue reading on &lt;strong&gt;fabrithedev.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/ai&quot;&gt;#ai&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-09T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/tidewave-changed-how-i-feel-about-ai-assisted-coding</id>
    <title>🔗 Finding my way with AI coding agents</title>
    <updated>2026-05-09T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/every-layer-of-review-makes-you-10x-slower" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Every layer of approval in a process makes it approximately 10x slower in wall-clock time, primarily due to waiting and coordination overhead. A simple bug fix takes 30 minutes to code, 5 hours to peer review, a week for design approval, and 12 weeks if another team must schedule it.&lt;/p&gt;
&lt;p&gt;Sustainable speed improvement requires fewer reviews rather than faster reviewers, as AI cannot overcome latency bottlenecks in approval pipelines. Quality improvement should focus on system-wide engineering practices and trust, following Deming&apos;s philosophy, rather than adding more QA layers that create perverse incentives and reduce actual quality.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://apenwarr.ca/log/20260316&quot;&gt;Continue reading on &lt;strong&gt;apenwarr.ca&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-09T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/every-layer-of-review-makes-you-10x-slower</id>
    <title>🔗 Every layer of review makes you 10x slower</title>
    <updated>2026-05-09T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/explains-other-superpowers" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Did you know that the &lt;code&gt;EXPLAIN&lt;/code&gt; command in PostgreSQL has several lesser-known options that can significantly enhance your troubleshooting capabilities? The &lt;code&gt;BUFFERS&lt;/code&gt; option shows where your data was sourced—whether from shared buffers or disk—helping you diagnose cache issues or memory constraints. You can then track memory usage during query planning with &lt;code&gt;MEMORY&lt;/code&gt;. For write-heavy operations, the &lt;code&gt;WAL&lt;/code&gt; option reveals detailed logging metrics, useful for bulk loads or updates. Want to replicate your environment? The &lt;code&gt;SETTINGS&lt;/code&gt; option outlines your configuration settings, while the &lt;code&gt;VERBOSE&lt;/code&gt; flag provides an expanded view of the query planner&apos;s operations. Combine these options, like &lt;code&gt;EXPLAIN (ANALYZE, BUFFERS, WAL, SETTINGS)&lt;/code&gt;, for a comprehensive picture of your query execution and database health.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://richyen.com/postgres/2026/03/23/explain_options.html&quot;&gt;Continue reading on &lt;strong&gt;richyen.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/postgresql&quot;&gt;#postgresql&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-08T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/explains-other-superpowers</id>
    <title>🔗 EXPLAIN&apos;s other superpowers</title>
    <updated>2026-05-08T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/thinking-elixir-podcast-302-beam-in-your-pocket" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;News includes Mob bringing BEAM-on-device native mobile dev to Elixir, Folio for print-quality PDFs via Rustler NIF, Oban v2.22 &amp; Pro v1.7.0 released, LiveVue v1.1 with Node-less SSR, and more!&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://podcast.thinkingelixir.com/302&quot;&gt;Continue reading on &lt;strong&gt;podcast.thinkingelixir.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-08T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/thinking-elixir-podcast-302-beam-in-your-pocket</id>
    <title>🔗 Thinking Elixir Podcast 302: BEAM in Your Pocket</title>
    <updated>2026-05-08T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/most-devs-ignore-git-worktree-heres-why-theyre-wrong" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;I used to dread the inefficiency of branch switching in Git. Switching branches often disrupted my workflow, forcing me to stash changes and lose my train of thought. Then I discovered &lt;code&gt;git worktree&lt;/code&gt;, which transformed my experience by allowing multiple clean working directories for the same repository. With &lt;code&gt;git worktree add&lt;/code&gt;, I could create a new folder for a branch, eliminating the need to stash or lose context. This approach enables parallel development, so I could run tests in one directory while coding in another, minimizing context switches. Yet, many developers ignore it, thinking it’s only for advanced users, even as they spend hours on outdated workflows. After using &lt;code&gt;git worktree&lt;/code&gt;, I could never go back. It’s not flashy, but it’s incredibly effective.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://dev.ongoro.top/post/most-devs-ignore-git-worktree-heres-why-theyre-wrong&quot;&gt;Continue reading on &lt;strong&gt;dev.ongoro.top&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/git&quot;&gt;#git&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-08T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/most-devs-ignore-git-worktree-heres-why-theyre-wrong</id>
    <title>🔗 Most devs ignore git worktree. Here&apos;s why they&apos;re wrong</title>
    <updated>2026-05-08T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/using-generators-for-phpunit-data-providers" rel="alternate"/>
    <content type="html">&lt;p&gt;When writing PHPUnit tests, data providers are one of the simplest ways to increase coverage without duplicating test logic. Most examples use arrays, but PHP generators (&lt;code&gt;yield&lt;/code&gt;) are often a better fit: they’re more expressive, memory-efficient, and easier to extend.&lt;/p&gt;
&lt;p&gt;This post walks through a clean, generic approach to using generators for PHPUnit data providers.&lt;/p&gt;
&lt;h1&gt;Why use generators instead of arrays?&lt;/h1&gt;
&lt;p&gt;A traditional data provider might look like this:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-php&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;provideCases&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;array&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;        &lt;span class=&quot;string&quot;&gt;&amp;#39;case A&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;input-a&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;expected-a&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span class=&quot;string&quot;&gt;&amp;#39;case B&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;input-b&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;expected-b&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works, but generators give you a few advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No need to build a full array in memory&lt;/li&gt;
&lt;li&gt;Named cases are more natural with &lt;code&gt;yield&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Easier to compose or split into smaller logical chunks&lt;/li&gt;
&lt;li&gt;Better readability when cases grow&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;A simple example&lt;/h1&gt;
&lt;p&gt;Imagine you’re testing a service that transforms input values:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-php&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword-type&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;ValueTransformer&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function-method&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$value&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span class=&quot;keyword-return&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;strtoupper&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;$value&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of writing multiple test methods, you can use a generator-based data provider:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-php&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;keyword-import&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;PHPUnit&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Framework&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Attributes&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;DataProvider&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;keyword-import&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;PHPUnit&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Framework&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Attributes&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword-type&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;ValueTransformerTest&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;TestCase&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;#[&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;#[&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;DataProvider&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;provideTransformCases&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function-method&quot;&gt;it_transforms_values&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$input&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$expected&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;void&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span class=&quot;variable&quot;&gt;$service&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;constructor&quot;&gt;ValueTransformer&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;        &lt;span class=&quot;variable&quot;&gt;$result&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$service&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;$input&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;        &lt;span class=&quot;variable-builtin&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;assertSame&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;$expected&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$result&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function-method&quot;&gt;provideTransformCases&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;Generator&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;        &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;lowercase&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;hello&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;HELLO&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;        &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;mixed case&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;HeLLo&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;HELLO&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;        &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;already uppercase&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;WORLD&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;WORLD&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Making test cases more expressive&lt;/h1&gt;
&lt;p&gt;Generators really shine when your inputs are objects instead of primitives.&lt;/p&gt;
&lt;p&gt;For example, testing a query modifier:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-php&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword-type&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;QueryModifier&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function-method&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;QueryBuilder&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$field&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$direction&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;QueryBuilder&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span class=&quot;keyword-return&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;orderBy&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;$field&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$direction&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your test can focus on behavior while the generator defines the variations:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-php&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;#[&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;#[&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;DataProvider&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;provideSortingCases&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;it_applies_sorting&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$field&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$direction&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;void&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;$query&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;Mockery&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;QueryBuilder&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;shouldReceive&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;orderBy&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;        &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;once&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;        &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;$field&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$direction&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;andReturnSelf&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;$modifier&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;constructor&quot;&gt;QueryModifier&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;$modifier&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$field&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$direction&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;provideSortingCases&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;Generator&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;sort by name&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;asc&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;sort by created_at desc&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;created_at&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;desc&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;sort by nested field&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;user.email&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;asc&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The test stays minimal, while the generator clearly documents the supported scenarios.&lt;/p&gt;
&lt;h1&gt;Composing generators&lt;/h1&gt;
&lt;p&gt;One underrated benefit is that generators can be composed. You can split cases into smaller methods and reuse them:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-php&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;provideSortingCases&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;Generator&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield from&lt;/span&gt; &lt;span class=&quot;variable-builtin&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;basicFields&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield from&lt;/span&gt; &lt;span class=&quot;variable-builtin&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;nestedFields&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;basicFields&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;Generator&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;name asc&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;asc&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;created_at desc&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;created_at&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;desc&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;nestedFields&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;Generator&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;user email&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;user.email&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;asc&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This keeps large test suites maintainable without sacrificing clarity.&lt;/p&gt;
&lt;h1&gt;When to prefer generators&lt;/h1&gt;
&lt;p&gt;Generators are especially useful when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have many test cases&lt;/li&gt;
&lt;li&gt;Test data is constructed dynamically&lt;/li&gt;
&lt;li&gt;You want to group or reuse subsets of cases&lt;/li&gt;
&lt;li&gt;Memory usage might become a concern&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For small, static datasets, arrays are perfectly fine. But once your data providers grow, generators tend to scale much better.&lt;/p&gt;
&lt;h1&gt;Final thoughts&lt;/h1&gt;
&lt;p&gt;Using generators in PHPUnit data providers is a small change that improves readability and flexibility. Tests become easier to extend, and the intent of each case is clearer thanks to named yields.&lt;/p&gt;
&lt;p&gt;If you’re already relying on data providers, switching from arrays to generators is a low-effort improvement that pays off quickly in larger test suites.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/php&quot;&gt;#php&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/testing&quot;&gt;#testing&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-07T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/using-generators-for-phpunit-data-providers</id>
    <title>🐥 Using generators for PHPUnit data providers</title>
    <updated>2026-05-07T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/php-attributes-in-laravel-13-the-ultimate-guide-36-new-attributes" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;New Laravel 13 went all-in on &lt;a href=&quot;https://www.php.net/manual/en/language.attributes.overview.php&quot;&gt;PHP attributes&lt;/a&gt;. Properties like &lt;code&gt;$fillable&lt;/code&gt;, &lt;code&gt;$guarded&lt;/code&gt;, and &lt;code&gt;$hidden&lt;/code&gt; that you&apos;ve been defining on models for years can now be declared as class-level attributes: &lt;code&gt;#[Fillable]&lt;/code&gt;, &lt;code&gt;#[Guarded]&lt;/code&gt;, and &lt;code&gt;#[Hidden]&lt;/code&gt;. The same applies to job configuration, console command signatures, middleware, and more.&lt;/p&gt;
&lt;p&gt;In this article, I&apos;ll walk you through &lt;strong&gt;every PHP attribute&lt;/strong&gt; available in Laravel 13 — both the new ones and those that existed before — with practical code examples for each.&lt;/p&gt;
&lt;p&gt;Important notice: those attributes are &lt;strong&gt;optional&lt;/strong&gt;, and you may use the old syntax in all those cases. The attributes are NOT breaking changes, just the new alternatives.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://laraveldaily.com/post/php-attributes-in-laravel-13-the-ultimate-guide-36-new-attributes&quot;&gt;Continue reading on &lt;strong&gt;laraveldaily.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/laravel&quot;&gt;#laravel&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/php&quot;&gt;#php&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-07T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/php-attributes-in-laravel-13-the-ultimate-guide-36-new-attributes</id>
    <title>🔗 PHP attributes in Laravel 13: The ultimate guide (36 new attributes!)</title>
    <updated>2026-05-07T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/debugging-slow-ecto-queries-with-appsignal" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;A sports car can only be driven as fast as the road it&apos;s on. In Phoenix applications, the database often becomes that bottleneck, impacting performance.  AppSignal aids in detecting slow Ecto queries by auto-instrumenting them. It allows developers to pinpoint the specific queries causing performance issues. The tool highlights slow queries in the performance section, showing them ranked by impact based on frequency and duration. To resolve issues, ensuring tables are indexed properly and avoiding unbounded queries are key strategies. For common problems like N+1 queries, employing techniques such as limiting query results or offloading work to materialized views can greatly enhance performance. After making adjustments, it’s crucial to verify that these changes effectively reduce latency by checking AppSignal&apos;s insights post-deployment.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://blog.appsignal.com/2026/04/02/debugging-slow-ecto-queries-with-appsignal&quot;&gt;Continue reading on &lt;strong&gt;blog.appsignal.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-07T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/debugging-slow-ecto-queries-with-appsignal</id>
    <title>🔗 Debugging slow ecto queries with AppSignal</title>
    <updated>2026-05-07T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/did-contexts-kill-phoenix" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;The author reflects on the challenges faced by the Phoenix framework, particularly how the introduction of contexts may have hindered its adoption among new developers.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://arrowsmithlabs.com/blog/did-contexts-kill-phoenix&quot;&gt;Continue reading on &lt;strong&gt;arrowsmithlabs.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-06T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/did-contexts-kill-phoenix</id>
    <title>🔗 Did contexts kill Phoenix?</title>
    <updated>2026-05-06T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/on-knowing-what-code-to-throw-away" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;This article discusses a shift in coding practices due to the emergence of agentic coding tools. The emphasis has moved from merely writing code to knowing what to remove, as the ability to generate code has become abundant while the skill of effective curation remains scarce.&lt;/p&gt;
&lt;p&gt;The article compares this change to Michelangelo&apos;s sculpting process, where the focus is on revealing the form within the marble rather than adding more material. As a result, software engineers now face the challenge of discerning which elements are essential, recognizing that the skill of editing is more crucial than the act of building itself.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://matthewsinclair.com/blog/0188-on-knowing-what-code-to-throw-away&quot;&gt;Continue reading on &lt;strong&gt;matthewsinclair.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/ai&quot;&gt;#ai&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-06T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/on-knowing-what-code-to-throw-away</id>
    <title>🔗 On knowing what code to throw away</title>
    <updated>2026-05-06T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/what-i-learned-from-nearly-1-000-interviews-at-amazon" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;I spent over a decade as an Amazon Bar Raiser, interviewing nearly 1,000 candidates across all levels. A surprising trend emerged: technical skills mattered less than how candidates presented themselves. Many who excelled technically still failed due to poor storytelling in behavioral interviews. While preparation for technical skills is concrete and quantifiable, non-technical preparation can be neglected entirely. I found that spending even a small portion of your prep time on crafting clear, concise personal stories can dramatically improve your interview performance. Ultimately, interviews should be viewed as auditions for teamwork rather than exams to be passed, emphasizing the importance of fit and personality in hiring decisions.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://alifeengineered.substack.com/p/what-i-learned-from-nearly-1000-interviews&quot;&gt;Continue reading on &lt;strong&gt;alifeengineered.substack.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-06T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/what-i-learned-from-nearly-1-000-interviews-at-amazon</id>
    <title>🔗 What I learned from nearly 1,000 interviews at Amazon</title>
    <updated>2026-05-06T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/fixing-the-ondrej-nginx-ppa-403-error-on-laravel-forge-servers" rel="alternate"/>
    <content type="html">&lt;p&gt;If you&apos;re running Ubuntu 22.04 on a Laravel Forge-managed server, you may have recently started seeing this error during &lt;code&gt;apt update&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Err:7 https://ppa.launchpadcontent.net/ondrej/nginx/ubuntu jammy InRelease
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  403  Forbidden
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;E: The repository &amp;#39;https://ppa.launchpadcontent.net/ondrej/nginx/ubuntu jammy InRelease&amp;#39; is no longer signed.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;What&apos;s happening?&lt;/h1&gt;
&lt;p&gt;Forge historically added the &lt;code&gt;ondrej/nginx&lt;/code&gt; PPA to its servers to get newer versions of nginx. Ondrej Surý (the maintainer) has since moved this PPA to a &lt;strong&gt;paid subscription model&lt;/strong&gt;, so unauthenticated access now returns a 403. Your server still has the old PPA entry, but it can no longer reach it.&lt;/p&gt;
&lt;p&gt;You&apos;ll notice the &lt;code&gt;ondrej/php&lt;/code&gt; PPA still works fine — that one remains freely available.&lt;/p&gt;
&lt;h1&gt;The fix&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Find the file that references the broken PPA:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;ondrej/nginx&quot;&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/etc/apt/sources.list.d/&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Remove it (the filename will match what grep returned above):&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/etc/apt/sources.list.d/ondrej-ubuntu-nginx-jammy.list&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/etc/apt/sources.list.d/ondrej-ubuntu-nginx-jammy.list.distUpgrade&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: &lt;code&gt;add-apt-repository --remove ppa:ondrej/nginx&lt;/code&gt; won&apos;t work here — Launchpad returns a 403 before it can resolve the PPA name.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Run &lt;code&gt;apt update&lt;/code&gt; again. If you need a newer version of nginx than Ubuntu&apos;s default (1.18.x), add the official nginx.org repository instead:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-fsSL&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;https://nginx.org/keys/nginx_signing.key&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;gpg&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--dearmor&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/usr/share/keyrings/nginx-archive-keyring.gpg&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;function-builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu jammy nginx&quot;&lt;/span&gt;&lt;span class=&quot;text&quot;&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;tee&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/etc/apt/sources.list.d/nginx.list&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;update&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;nginx&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Do you need a newer nginx?&lt;/h1&gt;
&lt;p&gt;For most Laravel applications, Ubuntu&apos;s default nginx is perfectly adequate. Unless you have a specific reason to run a cutting-edge nginx version, removing the PPA and sticking with the default is the simplest and most stable path.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/laravel&quot;&gt;#laravel&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/linux&quot;&gt;#linux&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/sysadmin&quot;&gt;#sysadmin&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-05T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/fixing-the-ondrej-nginx-ppa-403-error-on-laravel-forge-servers</id>
    <title>🐥 Fixing the Ondrej Nginx PPA 403 error on Laravel Forge servers</title>
    <updated>2026-05-05T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/collaborative-editing-in-prosemirror" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Collaborative editing can feel daunting, yet ProseMirror makes it accessible. I learned that ProseMirror facilitates real-time collaboration through its robust data structure and undo manager. The library supports multiple users editing the same document without conflicts by leveraging patches and operational transformation. Each user’s changes are tracked and synchronized seamlessly, which allows for a smooth collaborative experience. The implementation of collaborative features can also leverage different backends like WebSockets efficiently, enabling near-instant updates. ProseMirror&apos;s extensible architecture makes it easy to customize features according to specific project needs. Overall, integrating ProseMirror can significantly enhance user interaction in text-based applications.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://marijnhaverbeke.nl/blog/collaborative-editing.html&quot;&gt;Continue reading on &lt;strong&gt;marijnhaverbeke.nl&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/javascript&quot;&gt;#javascript&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/frontend&quot;&gt;#frontend&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-05T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/collaborative-editing-in-prosemirror</id>
    <title>🔗 Collaborative Editing in ProseMirror</title>
    <updated>2026-05-05T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/the-two-kinds-of-error" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;In this article, errors in software development are divided into two main categories: expected errors and unexpected errors. Expected errors occur during normal operations, such as validation or network errors, and can be handled gracefully, whereas unexpected errors indicate bugs and should cause the program to crash or panic.&lt;/p&gt;
&lt;p&gt;The distinction between these errors can depend on the context, from quick scripts where all errors may be seen as unexpected to critical systems where many errors are anticipated. Languages like Rust tend to classify more errors as expected, promoting reliability, while JavaScript might be more lenient, showcasing different philosophies in error handling within programming.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://evanhahn.com/the-two-kinds-of-error/&quot;&gt;Continue reading on &lt;strong&gt;evanhahn.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/rust&quot;&gt;#rust&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/javascript&quot;&gt;#javascript&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-05T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/the-two-kinds-of-error</id>
    <title>🔗 The two kinds of error</title>
    <updated>2026-05-05T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/the-git-commands-i-run-before-reading-any-code" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;The first thing I do with a new codebase isn’t dive into the files; I run specific &lt;code&gt;git&lt;/code&gt; commands to assess its health. The &lt;code&gt;git log --format&lt;/code&gt; command can reveal the 20 most changed files over the past year, highlighting those at risk of being &quot;codebase drag.&quot; A 2005 Microsoft study showed that churn metrics predict defects better than complexity metrics. I cross-reference high-churn files with &lt;code&gt;git log -i -E --grep&lt;/code&gt; for bug keywords to identify the most dangerous areas. Assessing contributor activity with &lt;code&gt;git shortlog -sn --no-merges&lt;/code&gt; also helps determine the bus factor and stability of knowledge in the team. Declining commit trends signal team momentum loss and issues in deployment are indicated by revert frequencies. These simple commands offer a quick snapshot of a codebase’s challenges, guiding me on where to focus my attention first.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://piechowski.io/post/git-commands-before-reading-code/&quot;&gt;Continue reading on &lt;strong&gt;piechowski.io&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/git&quot;&gt;#git&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-04T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/the-git-commands-i-run-before-reading-any-code</id>
    <title>🔗 The Git commands I run before reading any code</title>
    <updated>2026-05-04T17:00:00Z</updated>
  </entry>
</feed>