<?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-08-10T17:00:00Z</updated>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/top-10-backend-frameworks-for-enterprise-development-in-2026" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;In 2026, choosing a backend framework is more nuanced than ever. Enterprise teams now balance performance, ecosystem maturity, and developer availability against long-term costs. The guide delves into the top frameworks crucial for enterprise development this year. It outlines the strengths and limitations of each framework, guiding teams on aligning choices with their specific constraints rather than popular trends.&lt;/p&gt;
&lt;p&gt;Among the contenders, Spring Boot remains dominant due to its vast ecosystem, but alternatives like Quarkus and FastAPI offer compelling advantages in speed and memory efficiency. For Python enthusiasts, Django and FastAPI cater to varying needs, emphasizing security and async capabilities. This year’s choices underscore the importance of matching frameworks to organizational goals, serving both current needs and future demands with a focus on scalability, security, and developer capacity.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://blog.vaadin.com/enterprise-backend-frameworks&quot;&gt;Continue reading on &lt;strong&gt;blog.vaadin.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/python&quot;&gt;#python&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/django&quot;&gt;#django&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/java&quot;&gt;#java&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-10T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/top-10-backend-frameworks-for-enterprise-development-in-2026</id>
    <title>🔗 Top 10 backend frameworks for enterprise development in 2026</title>
    <updated>2026-08-10T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/ai-coding-tip-027-force-code-standards" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Style errors double when nobody enforces them. Maximiliano Contieri outlines a critical aspect of AI coding: enforcing standards. A CodeRabbit analysis revealed that AI-generated code contains about 1.7 times more defects than human-written code, particularly in the areas of naming and style consistency. Instead of relying on human memory to remember coding standards, Contieri advocates for integrating machine-checkable rules through hooks and validators. This can block violations before any human review occurs, allowing developers to focus on design rather than mechanical checks. With each violation logged as a rule, the enforcement grows stricter over time, promising a measurable drop in defects and allowing for faster reviews. This shift emphasizes the need for automation in maintaining code quality.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://maxicontieri.substack.com/p/ai-coding-tip-027-force-code-standards&quot;&gt;Continue reading on &lt;strong&gt;maxicontieri.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/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;a href=&quot;https://www.yellowduck.be/tags/testing&quot;&gt;#testing&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-10T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/ai-coding-tip-027-force-code-standards</id>
    <title>🔗 AI coding tip 027 - Force code standards</title>
    <updated>2026-08-10T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/jurassic-park-computers-in-excruciating-detail" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;What does the technology in &lt;em&gt;Jurassic Park&lt;/em&gt; reveal about the era it was made? Fabien Sanglard delves into the specifics of every computer and software showcased in the film. From the &lt;em&gt;Apple Powerbook 100&lt;/em&gt; to &lt;em&gt;SGI IRIS Crimson&lt;/em&gt;, he breaks down the specs and functionalities of these machines, noting how they contributed to the movie&apos;s iconic scenes. The article highlights the impressive real-time 3D animations powered by &lt;em&gt;SGI workstations&lt;/em&gt; and discusses the hefty value of the computer hardware loaned during production, which rose to $4 million in today’s dollars. By examining the devices in the chaos of the Control Room, Sanglard provides a refreshing take on a nostalgic element of the film that many may overlook. Whether it’s the monitors or the personal digital assistants, each piece tells a story about the technology of the early &apos;90s.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://fabiensanglard.net/jurrasic_park_computers/index.html&quot;&gt;Continue reading on &lt;strong&gt;fabiensanglard.net&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;/p&gt;</content>
    <published>2026-08-10T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/jurassic-park-computers-in-excruciating-detail</id>
    <title>🔗 Jurassic Park computers in excruciating detail</title>
    <updated>2026-08-10T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/are-we-offloading-too-much-of-our-thinking-to-ai" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;This article examines the growing trend of offloading cognitive tasks to AI, including both trivial and complex decisions. Yennie Jun illustrates this by referencing Ken Liu&apos;s short story &quot;The Perfect Match,&quot; where the protagonist relies on an AI assistant for personal choices, reflecting a concerning loss of autonomy. The piece discusses real-life encounters with individuals who let AI dictate their thoughts and decisions, raising questions about the impact of this dependency on critical thinking and originality. Jun encourages a balance between leveraging AI for efficiency and retaining our own thought processes, exploring how automation in daily tasks might enhance productivity versus leading to lazy thinking. Ultimately, the article provokes a deeper introspection about the consequences of allowing AI to make decisions that shape our identities and desires.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://www.artfish.ai/p/offloading-thinking-to-ai&quot;&gt;Continue reading on &lt;strong&gt;www.artfish.ai&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-08-09T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/are-we-offloading-too-much-of-our-thinking-to-ai</id>
    <title>🔗 Are we offloading too much of our thinking to AI?</title>
    <updated>2026-08-09T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/lobste-rs-is-now-running-on-sqlite" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Lobste.rs has officially migrated from MariaDB to SQLite. This decision, which began in August 2018 with plans initially focused on PostgreSQL, culminated successfully over the past weekend. The new architecture has shown performance improvements, including reduced CPU and memory usage, making the site feel snappier. The Lobsters Rails application now operates on a single VPS with an SQLite database file of approximately 3.8GB, supplemented by a cache and queue database, and a rack_attack database. The migration PR optimized the code significantly, reflecting a modern approach to server management with minimal resources. This case study serves as a potent reminder of how effective SQLite can be for smaller applications in 2026.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://simonwillison.net/2026/Jul/14/lobsters-sqlite/#atom-everything&quot;&gt;Continue reading on &lt;strong&gt;simonwillison.net&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/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/sqlite&quot;&gt;#sqlite&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-09T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/lobste-rs-is-now-running-on-sqlite</id>
    <title>🔗 Lobste.rs is now running on SQLite</title>
    <updated>2026-08-09T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/i-couldve-rickrolled-the-entire-fifa-world-cup-all-i-needed-was-my-id" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;What if a simple registration process could expose live broadcasts of one of the biggest sporting events in the world? In this article, the author outlines a shocking incident where a flaw in FIFA&apos;s security allowed access to the Streaming Management panel for the FIFA World Cup 2026. By just registering as a football agent, they gained entry to live match feeds, RTMP ingest URLs, and even the ability to manipulate match statistics. This breach highlighted a critical failure in server-side security, where backend APIs trusted authenticated users without proper role checks. Despite multiple attempts to report the issue to FIFA and related entities, the author faced significant hurdles in getting the problem acknowledged. Ultimately, the vulnerability was patched quickly, but FIFA did not respond or thank the whistleblower, raising concerns about security practices in mega sporting events.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://bobdahacker.com/blog/fifa-hack&quot;&gt;Continue reading on &lt;strong&gt;bobdahacker.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;a href=&quot;https://www.yellowduck.be/tags/auth&quot;&gt;#auth&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-09T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/i-couldve-rickrolled-the-entire-fifa-world-cup-all-i-needed-was-my-id</id>
    <title>🔗 I could&apos;ve rickrolled the entire FIFA World Cup. All I needed was my ID.</title>
    <updated>2026-08-09T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/distributing-a-go-cli-via-a-homebrew-tap" rel="alternate"/>
    <content type="html">&lt;p&gt;Homebrew is the package manager that macOS developers reach for first. If you want your CLI tool to feel like a first-class citizen on macOS — installable with a single command, updatable with &lt;code&gt;brew upgrade&lt;/code&gt;, and auto-completed by the shell — a Homebrew tap is the right solution. This post covers how to set one up for a Go CLI, including the formula, CI validation, and the update workflow.&lt;/p&gt;
&lt;h1&gt;What is a tap?&lt;/h1&gt;
&lt;p&gt;A Homebrew &lt;em&gt;tap&lt;/em&gt; is just a GitHub repository named &lt;code&gt;homebrew-&lt;something&gt;&lt;/code&gt;. Once a user runs &lt;code&gt;brew tap org/something&lt;/code&gt;, Homebrew knows to look for formulas in that repo. From that point on, &lt;code&gt;brew install org/something/mycli&lt;/code&gt; works exactly like installing any official Homebrew package.&lt;/p&gt;
&lt;p&gt;The naming convention is the only magic: no registration, no approval process. Anyone can host a tap.&lt;/p&gt;
&lt;h1&gt;The formula&lt;/h1&gt;
&lt;p&gt;Formulas live in a &lt;code&gt;Formula/&lt;/code&gt; directory and are written in Ruby using Homebrew&apos;s DSL. For a pre-built Go binary, the formula is remarkably simple:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-ruby&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-keyword-type&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;l-type&quot;&gt;MyCli&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;&lt;&lt;/span&gt; &lt;span class=&quot;l-type&quot;&gt;Formula&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;l-function-call&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;CLI interface for My App&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;  &lt;span class=&quot;l-function-call&quot;&gt;homepage&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;https://example.com&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;l-function-call&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;https://cdn.example.com/mycli/5c7b6ef47a682ffe.../mycli-macos.tar.gz&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;  &lt;span class=&quot;l-function-call&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;1.47.0&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;6&quot;&gt;  &lt;span class=&quot;l-function-call&quot;&gt;sha256&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;48e146382b0f527549328da4bed9d90f2d584e1b05a8f94846cf47c3c66a9343&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;7&quot;&gt;  &lt;span class=&quot;l-function-call&quot;&gt;license&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;MIT&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;9&quot;&gt;  &lt;span class=&quot;l-keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;l-function&quot;&gt;install&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;10&quot;&gt;    &lt;span class=&quot;l-variable&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;mycli&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;11&quot;&gt;  &lt;span class=&quot;l-keyword-function&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;13&quot;&gt;  &lt;span class=&quot;l-function-call&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;14&quot;&gt;    &lt;span class=&quot;l-function-call&quot;&gt;system&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-special&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;l-punctuation-special&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;/mycli&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;--version&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;15&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;16&quot;&gt;&lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are only four things that change on each release:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt; — points to the new tarball on the CDN&lt;/li&gt;
&lt;li&gt;&lt;code&gt;version&lt;/code&gt; — the semver string shown by &lt;code&gt;brew info&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sha256&lt;/code&gt; — the checksum Homebrew verifies before unpacking&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;install&lt;/code&gt; block copies the binary into the Homebrew prefix (&lt;code&gt;/opt/homebrew/bin/&lt;/code&gt; on Apple Silicon, &lt;code&gt;/usr/local/bin/&lt;/code&gt; on Intel). The &lt;code&gt;test&lt;/code&gt; block is a smoke test Homebrew runs after install — here, just confirming the binary exits cleanly when asked for its version.&lt;/p&gt;
&lt;h1&gt;Building a universal macOS binary&lt;/h1&gt;
&lt;p&gt;The URL points to a &lt;code&gt;.tar.gz&lt;/code&gt; containing a single binary. Rather than shipping separate arm64 and x86_64 tarballs and maintaining two formula entries, we use &lt;code&gt;lipo&lt;/code&gt; to merge them into one universal binary:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-make&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-keyword&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;build-binary-mac&lt;/span&gt;&lt;span class=&quot;l-text&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;    @echo &quot;Building for macos&quot;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;    GOOS=darwin GOARCH=arm64 go build -o mycli-arm .
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;    GOOS=darwin GOARCH=amd64 go build -o mycli-x86 .
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;    lipo -create -output mycli mycli-x86 mycli-arm
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;6&quot;&gt;    tar czf ./mycli-macos.tar.gz mycli
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;7&quot;&gt;    rm -f mycli-x86 mycli-arm mycli
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;8&quot;&gt;&lt;/span&gt;&lt;span class=&quot;l-keyword&quot;&gt;endef&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;GOOS=darwin GOARCH=arm64 go build&lt;/code&gt; and &lt;code&gt;GOOS=darwin GOARCH=amd64 go build&lt;/code&gt; produce two binaries. &lt;code&gt;lipo -create&lt;/code&gt; stitches them into a Fat Binary that macOS automatically runs under the native architecture. The result: one tarball, one formula URL, works on both Apple Silicon and Intel Macs without any user-facing complexity.&lt;/p&gt;
&lt;h1&gt;The CDN URL strategy&lt;/h1&gt;
&lt;p&gt;The binary is uploaded to object storage (DigitalOcean Spaces in our case, but S3 or any public CDN works equally well). The URL path includes the git commit SHA:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-make&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-function&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;l-text&quot;&gt;//cdn.example.com/mycli/{git-sha}/mycli-macos.tar.gz&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Embedding the commit SHA rather than the version tag means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uploads are naturally immutable — the same SHA always points to the same build&lt;/li&gt;
&lt;li&gt;Multiple release candidates for the same version cannot collide&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;create-index.py&lt;/code&gt; script (which generates a &lt;code&gt;index.json&lt;/code&gt; listing all build artifacts) uses the same SHA, keeping the release artefacts coherent&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Updating the formula&lt;/h1&gt;
&lt;p&gt;When a new version ships:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build and upload the tarball: &lt;code&gt;make build-all&lt;/code&gt; produces &lt;code&gt;mycli-macos.tar.gz&lt;/code&gt;; CI uploads it to the CDN under the new commit SHA.&lt;/li&gt;
&lt;li&gt;Compute the SHA256 of the tarball: &lt;code&gt;sha256sum mycli-macos.tar.gz&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Update three lines in the formula: &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt;, &lt;code&gt;sha256&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Open a PR against the tap repository&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The tap CI then validates the change before it merges (more on that below).&lt;/p&gt;
&lt;h1&gt;CI: validating formulas with &lt;code&gt;brew test-bot&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;The tap repo includes a GitHub Actions workflow that runs on every push and pull request:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-yaml&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;brew test-bot&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;l-property&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;  &lt;span class=&quot;l-property&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;    &lt;span class=&quot;l-property&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;  &lt;span class=&quot;l-property&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;7&quot;&gt;&lt;span class=&quot;l-property&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;8&quot;&gt;  &lt;span class=&quot;l-property&quot;&gt;test-bot&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;9&quot;&gt;    &lt;span class=&quot;l-property&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;10&quot;&gt;      &lt;span class=&quot;l-property&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;11&quot;&gt;        &lt;span class=&quot;l-property&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;macos-latest&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;12&quot;&gt;    &lt;span class=&quot;l-property&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;${{ matrix.os }}&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;13&quot;&gt;    &lt;span class=&quot;l-property&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;14&quot;&gt;      &lt;span class=&quot;l-punctuation-delimiter&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;l-property&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;Homebrew/actions/setup-homebrew@main&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;16&quot;&gt;      &lt;span class=&quot;l-punctuation-delimiter&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;l-property&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;test-bot&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;--only-cleanup-before&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;17&quot;&gt;      &lt;span class=&quot;l-punctuation-delimiter&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;l-property&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;test-bot&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;--only-setup&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;18&quot;&gt;      &lt;span class=&quot;l-punctuation-delimiter&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;l-property&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;test-bot&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;--only-tap-syntax&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;19&quot;&gt;      &lt;span class=&quot;l-punctuation-delimiter&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;l-property&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;test-bot&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;--only-formulae&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;20&quot;&gt;        &lt;span class=&quot;l-property&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;github.event_name == &amp;#39;pull_request&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;21&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;22&quot;&gt;      &lt;span class=&quot;l-punctuation-delimiter&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;l-property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;Upload bottles as artifact&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;23&quot;&gt;        &lt;span class=&quot;l-property&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;always() &amp;&amp; github.event_name == &amp;#39;pull_request&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;24&quot;&gt;        &lt;span class=&quot;l-property&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;actions/upload-artifact@main&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;25&quot;&gt;        &lt;span class=&quot;l-property&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;26&quot;&gt;          &lt;span class=&quot;l-property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;bottles&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;27&quot;&gt;          &lt;span class=&quot;l-property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&amp;#39;*.bottle.*&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;brew test-bot&lt;/code&gt; is Homebrew&apos;s own CI harness. The steps do progressively more:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What it checks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--only-tap-syntax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ruby syntax, formula naming conventions, &lt;code&gt;brew audit&lt;/code&gt; rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--only-formulae&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Actually installs the formula and runs the &lt;code&gt;test do&lt;/code&gt; block&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Running on both &lt;code&gt;ubuntu-latest&lt;/code&gt; and &lt;code&gt;macos-latest&lt;/code&gt; catches issues specific to either platform — important because Homebrew has first-class Linux support (&lt;code&gt;Linuxbrew&lt;/code&gt;) and many teams use it in Docker-based CI.&lt;/p&gt;
&lt;h1&gt;CI: Merging PRs with &lt;code&gt;brew pr-pull&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;The second workflow handles the merge step:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-yaml&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;brew pr-pull&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;l-property&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;  &lt;span class=&quot;l-property&quot;&gt;pull_request_target&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;    &lt;span class=&quot;l-property&quot;&gt;types&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;labeled&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;l-property&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;7&quot;&gt;  &lt;span class=&quot;l-property&quot;&gt;pr-pull&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;l-property&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;contains(github.event.pull_request.labels.*.name, &amp;#39;pr-pull&amp;#39;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;9&quot;&gt;    &lt;span class=&quot;l-property&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;ubuntu-22.04&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;10&quot;&gt;    &lt;span class=&quot;l-property&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;11&quot;&gt;      &lt;span class=&quot;l-punctuation-delimiter&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;l-property&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;Homebrew/actions/setup-homebrew@main&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;12&quot;&gt;      &lt;span class=&quot;l-punctuation-delimiter&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;l-property&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;Homebrew/actions/git-user-config@main&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;14&quot;&gt;      &lt;span class=&quot;l-punctuation-delimiter&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;l-property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;Pull bottles&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;15&quot;&gt;        &lt;span class=&quot;l-property&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;16&quot;&gt;          &lt;span class=&quot;l-property&quot;&gt;HOMEBREW_GITHUB_API_TOKEN&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;${{ github.token }}&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;17&quot;&gt;          &lt;span class=&quot;l-property&quot;&gt;PULL_REQUEST&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;${{ github.event.pull_request.number }}&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;18&quot;&gt;        &lt;span class=&quot;l-property&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-punctuation-delimiter&quot;&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;19&quot;&gt;          &lt;span class=&quot;l-function-call&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;pr-pull&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;--debug&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;--tap=&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;l-constant&quot;&gt;GITHUB_REPOSITORY&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;l-constant&quot;&gt;PULL_REQUEST&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;20&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;21&quot;&gt;      &lt;span class=&quot;l-punctuation-delimiter&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;l-property&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;Homebrew/actions/git-try-push@main&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;22&quot;&gt;        &lt;span class=&quot;l-property&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;23&quot;&gt;          &lt;span class=&quot;l-property&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;${{ github.token }}&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;24&quot;&gt;          &lt;span class=&quot;l-property&quot;&gt;branch&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;main&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;25&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;26&quot;&gt;      &lt;span class=&quot;l-punctuation-delimiter&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;l-property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;Delete branch&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;27&quot;&gt;        &lt;span class=&quot;l-property&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;github.event.pull_request.head.repo.fork == false&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;28&quot;&gt;        &lt;span class=&quot;l-property&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;push&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;--delete&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;origin&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;l-constant&quot;&gt;BRANCH&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of merging PRs normally, a maintainer applies the &lt;code&gt;pr-pull&lt;/code&gt; label. &lt;code&gt;brew pr-pull&lt;/code&gt; then:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Downloads any pre-built bottles attached to the PR as GitHub Actions artifacts&lt;/li&gt;
&lt;li&gt;Commits the bottle checksums into the formula&lt;/li&gt;
&lt;li&gt;Pushes directly to &lt;code&gt;main&lt;/code&gt; and deletes the PR branch&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This mirrors the workflow used by the official Homebrew/homebrew-core tap and means the merge is always handled by Homebrew&apos;s own tooling rather than GitHub&apos;s merge button.&lt;/p&gt;
&lt;p&gt;For a simple pre-built binary formula (no compilation step, no bottle needed), this is mostly ceremony — but it is good practice because it keeps the tap workflow consistent with the broader Homebrew ecosystem and makes it easy to add bottles later if the build-from-source path is ever needed.&lt;/p&gt;
&lt;h1&gt;The user experience&lt;/h1&gt;
&lt;p&gt;After all of this, the user-facing story is clean:&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;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-comment&quot;&gt;# One-time setup&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;l-function-call&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;tap&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;myorg/mycli&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;l-comment&quot;&gt;# Install&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;&lt;span class=&quot;l-function-call&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;mycli&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;7&quot;&gt;&lt;span class=&quot;l-comment&quot;&gt;# Upgrade when a new version ships&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;8&quot;&gt;&lt;span class=&quot;l-function-call&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;upgrade&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;mycli&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And because Homebrew auto-discovers shell completions in &lt;code&gt;share/zsh/site-functions/&lt;/code&gt;, &lt;code&gt;share/bash-completion/&lt;/code&gt;, and similar paths, completions are picked up automatically if the binary generates them into those locations at install time.&lt;/p&gt;
&lt;h1&gt;Summary&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;homebrew-mycli&lt;/code&gt; repo&lt;/td&gt;
&lt;td&gt;The tap — a public GitHub repo Homebrew reads formulas from&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Formula/mycli.rb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Declares the URL, version, checksum, install path, and smoke test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Universal binary (&lt;code&gt;lipo&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;One tarball covers both Apple Silicon and Intel in a single formula entry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN + commit SHA URL&lt;/td&gt;
&lt;td&gt;Immutable, content-addressed artifact storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;brew test-bot&lt;/code&gt; CI&lt;/td&gt;
&lt;td&gt;Validates formula syntax and install on every PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;brew pr-pull&lt;/code&gt; CI&lt;/td&gt;
&lt;td&gt;Homebrew-native merge flow that handles bottle attachment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The tap repository itself is under 20 lines of real content. Most of the work is a one-time setup; after that, each release is a three-line diff to the formula and a label click to merge.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/golang&quot;&gt;#golang&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/mac&quot;&gt;#mac&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-08T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/distributing-a-go-cli-via-a-homebrew-tap</id>
    <title>🐥 Distributing a Go CLI via a Homebrew tap</title>
    <updated>2026-08-08T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/how-i-use-htmx-with-go" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Not sure how to structure your Go web application? Alex Edwards shares his insights on integrating HTMX to enhance interactivity seamlessly. His approach allows developers to maintain server-side HTML rendering consistency while minimizing JavaScript overhead. The article covers structuring HTML templates, managing partial and full-page responses, and handling redirects with HTMX often used in Go applications. Readers can explore the practical steps involved in setting up a project with HTMX, including file structuring and best practices for embedding assets in Go binaries. This guide is a valuable resource for those looking to build dynamic, user-friendly web applications with Go and HTMX.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://www.alexedwards.net/blog/how-i-use-htmx-with-go&quot;&gt;Continue reading on &lt;strong&gt;www.alexedwards.net&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/golang&quot;&gt;#golang&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;a href=&quot;https://www.yellowduck.be/tags/html&quot;&gt;#html&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-08T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/how-i-use-htmx-with-go</id>
    <title>🔗 How I use HTMX with Go</title>
    <updated>2026-08-08T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/the-strangest-hobby-at-the-table" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Handing everyone an LLM subscription might seem sufficient, but it actually limits their potential. A shared, self-hosted agent creates a configured environment tailored to company context, ensures that connected tools are accessible, and maintains clear boundaries. This article argues that companies benefit more from owning this layer rather than renting it. The piece outlines strategic ways for businesses to begin this transition, emphasizing that a self-hosted solution allows for greater customization and adaptability, which are crucial in a competitive landscape. It reassures organizations that starting small is a viable tactic, making it easier to implement significant changes.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://madewithlove.com/blog/the-strangest-hobby-at-the-table/&quot;&gt;Continue reading on &lt;strong&gt;madewithlove.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;a href=&quot;https://www.yellowduck.be/tags/ai&quot;&gt;#ai&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/devops&quot;&gt;#devops&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-08T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/the-strangest-hobby-at-the-table</id>
    <title>🔗 The strangest hobby at the table</title>
    <updated>2026-08-08T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/nginx-vs-caddy-vs-traefik-in-2026-i-ran-all-three-under-real-production-load" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Same Kubernetes cluster, same microservices, same TLS requirements. One proxy broke during ingress-nginx migration week. Guess which.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://medium.com/@developeryusuf/nginx-vs-caddy-vs-traefik-in-2026-i-ran-all-three-under-real-production-load-64e412f4062b&quot;&gt;Continue reading on &lt;strong&gt;medium.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;a href=&quot;https://www.yellowduck.be/tags/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/testing&quot;&gt;#testing&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/kubernetes&quot;&gt;#kubernetes&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-07T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/nginx-vs-caddy-vs-traefik-in-2026-i-ran-all-three-under-real-production-load</id>
    <title>🔗 Nginx vs Caddy vs Traefik in 2026 — I Ran All Three Under Real Production Load</title>
    <updated>2026-08-07T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/how-to-hire-engineers-for-strong-judgment-and-taste" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Judgment and taste are now critical hiring signals in engineering, as Sage Lazzaro explains. With AI taking over execution, developers must rapidly evaluate outputs and make informed product decisions. Companies are evolving their hiring practices, moving towards open-ended projects and assessing candidate thought processes rather than sheer knowledge. Candidates who struggle to articulate their decision-making raise red flags. Emphasis is placed on curiosity and nuanced thinking, reflecting the need for engineers to balance technical judgment with product sense. For example, roles like Forward Deployed Engineers require deep engagement with customer needs, making thoughtful decision-making more crucial than ever. This article highlights the shift in hiring strategies and the importance of identifying candidates who excel in both judgment and taste.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://leaddev.com/hiring/how-to-hire-engineers-for-strong-judgment-and-taste&quot;&gt;Continue reading on &lt;strong&gt;leaddev.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-08-07T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/how-to-hire-engineers-for-strong-judgment-and-taste</id>
    <title>🔗 How to hire engineers for strong judgment and taste</title>
    <updated>2026-08-07T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/why-pogacar-rides-on-a-car-tire-brand" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Why are pro cyclists like Tadej Pogačar riding on tires made by car manufacturers? Jan Heine reveals the surprising truth behind tire sponsorship in the Tour de France. While companies like Continental dominate road racing sponsorships, it&apos;s primarily car tires being promoted, not bike tires. With costs running into millions, small brands struggle to compete despite offering quality products. Heine highlights that while small makers find success in gravel races, the high stakes of pro cycling often favor larger brands willing to invest heavily. The article provides an insightful look into the financial strategies behind sponsorships in elite road racing, illustrating how marketing priorities can overshadow the sport&apos;s rich cycling heritage.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://www.renehersecycles.com/why-pogacar-rides-on-a-car-tire-brand/&quot;&gt;Continue reading on &lt;strong&gt;www.renehersecycles.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/sports&quot;&gt;#sports&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-07T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/why-pogacar-rides-on-a-car-tire-brand</id>
    <title>🔗 Why Pogačar rides on a car tire brand</title>
    <updated>2026-08-07T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/your-dropbox-is-now-a-skill-server" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;sx 2.0, a new app for Mac, Windows, and Linux, addresses the challenge of sharing AI skills among non-technical teams. By using a shared folder in Dropbox or similar services, teams can easily publish and synchronize their skills without needing git or terminal access. The app translates skills into various AI client formats seamlessly, allowing for effortless integration.&lt;/p&gt;
&lt;p&gt;Moreover, the release includes a pluggable extension system that enables teams to customize their workflows without bloating the core product. With built-in tools to monitor skill usage and ensure content quality, sx 2.0 targets marketing, legal, and sales teams who can now leverage AI without technical barriers through a simple, user-friendly interface. The emphasis on distribution aims to democratize access to AI knowledge across organizations.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://sleuth-io.github.io/sx/2026/07/10/your-dropbox-is-now-a-skill-server.html&quot;&gt;Continue reading on &lt;strong&gt;sleuth-io.github.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/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/announcement&quot;&gt;#announcement&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-06T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/your-dropbox-is-now-a-skill-server</id>
    <title>🔗 Your Dropbox is now a skill server</title>
    <updated>2026-08-06T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/kubernetes-dashboard-to-headlamp-a-step-by-step-guide" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Kubernetes Dashboard and Headlamp serve different roles in a cluster. Headlamp connects via &lt;code&gt;kubeconfig&lt;/code&gt;, allowing for desktop or in-cluster usage, while the Kubernetes Dashboard is strictly in-cluster. This article breaks down the processes for migrating from Dashboard to Headlamp. It highlights key differences in user experience, installation methods, and permissions management. A pre-migration checklist ensures a smooth transition, emphasizing the importance of understanding RBAC rules. By suggesting rollout plans, it gives teams structure in their migration approach. Ultimately, Headlamp simplifies managing multiple clusters with its user-friendly interface. It caters to both individual and shared access needs. This guide demystifies what it takes to move from Dashboard to Headlamp, equipping users with the information they need for a successful shift.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://kubernetes.io/blog/2026/07/13/kubernetes-dashboard-to-headlamp/&quot;&gt;Continue reading on &lt;strong&gt;kubernetes.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/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/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/kubernetes&quot;&gt;#kubernetes&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-06T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/kubernetes-dashboard-to-headlamp-a-step-by-step-guide</id>
    <title>🔗 Kubernetes Dashboard to Headlamp: A Step-by-Step Guide</title>
    <updated>2026-08-06T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/markdown-support-coming-in-netnewswire" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;NetNewsWire is about to embrace &lt;code&gt;source:markdown&lt;/code&gt; elements in its next release. This move makes it the first mainstream RSS client to fully support Markdown. The integration allows WordLand-written WordPress posts, which already feature the necessary RSS feeds, to be published and rendered in Markdown. Users can simply log into WordLand and start creating content that will automatically be displayed in NetNewsWire via this format. A GitHub thread has been initiated to address any questions regarding this capability.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://daveverse.org/2025/10/30/markdown-support-coming-in-netnewswire/&quot;&gt;Continue reading on &lt;strong&gt;daveverse.org&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/announcement&quot;&gt;#announcement&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-06T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/markdown-support-coming-in-netnewswire</id>
    <title>🔗 Markdown support coming in NetNewsWire</title>
    <updated>2026-08-06T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/introducing-mishka-chelekom-v0-0-9" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Mishka Chelekom v0.0.9 revolutionizes how AI agents interact with Phoenix UI. This release adds a built-in MCP server, enabling AI to utilize your components accurately. In addition, it boasts over 35 new headless components that are unstyled yet fully accessible, allowing complete design customization without sacrificing functionality. The new Kit allows users to reuse and restyle components easily without altering original code, preserving project integrity. With a growing community behind it and over 55,000 downloads, this version solidifies Mishka Chelekom&apos;s position as a powerful tool for developers looking to integrate AI effectively into their Phoenix applications.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://mishka.tools/blog/introducing-mishka-chelekom-v0.0.9-ai-native-phoenix-components-with-mcp-headless-ui-and-the-kit&quot;&gt;Continue reading on &lt;strong&gt;mishka.tools&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/ai&quot;&gt;#ai&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-08-05T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/introducing-mishka-chelekom-v0-0-9</id>
    <title>🔗 Introducing Mishka Chelekom v0.0.9</title>
    <updated>2026-08-05T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/kubernetes-architecture" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Kubernetes isn’t just another container orchestration tool; it’s an essential piece of cloud-native architecture that transformed how applications are managed. Neo Kim and Ayaan explain Kubernetes architecture, breaking down its components like the control plane and worker nodes. They illustrate how Kubernetes operates as a distributed system, ensuring applications stay available and resilient even as demands change. The authors emphasize the importance of concepts like desired state versus actual state, highlighting Kubernetes’ ability to self-heal and maintain the system’s equilibrium. This article not only provides a solid foundation for understanding how Kubernetes works but also makes a compelling case for why mastering its architecture is crucial for modern developers and architects.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://newsletter.systemdesign.one/p/kubernetes-architecture&quot;&gt;Continue reading on &lt;strong&gt;newsletter.systemdesign.one&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/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/kubernetes&quot;&gt;#kubernetes&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-05T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/kubernetes-architecture</id>
    <title>🔗 Kubernetes architecture</title>
    <updated>2026-08-05T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/best-and-worst-software-projects-what-developers-really-think" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;AI significantly accelerates software delivery, but human decision-making remains crucial. Engineers recently discussed the role of AI in their projects, emphasizing its ability to enhance speed without replacing critical thinking and strategic choices. The insights reflect a balanced perspective on technology&apos;s place in development processes, highlighting the importance of creativity and judgment. In a rapidly evolving tech landscape, understanding the boundaries of AI&apos;s capabilities is essential for developers who aim to leverage it effectively while retaining control over their projects.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://shiftmag.dev/developers-are-the-reason-behind-best-and-worst-parts-of-software-development-10670/&quot;&gt;Continue reading on &lt;strong&gt;shiftmag.dev&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-08-05T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/best-and-worst-software-projects-what-developers-really-think</id>
    <title>🔗 Best and worst software projects: What developers really think</title>
    <updated>2026-08-05T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/prefer-strict-tables-in-sqlite" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Strict tables in SQLite avoid datatype issues by enforcing rigid typing, preventing common mistakes like inserting text into integer columns. By appending &lt;code&gt;STRICT&lt;/code&gt; to a table’s definition, users ensure that type mismatches are not allowed during inserts and updates. In contrast to non-strict tables, which permit any type in any column, strict tables help maintain data integrity and clarity. This article highlights the advantages and limitations of using strict tables, including better validation and avoiding creating columns with invalid types. While strict tables might introduce some performance considerations, the author argues that the benefits in data integrity far outweigh the potential drawbacks. The preference for strict tables is supported by examples and a discussion on SQLite&apos;s flexible typing.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://evanhahn.com/prefer-strict-tables-in-sqlite/&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/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/sqlite&quot;&gt;#sqlite&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-04T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/prefer-strict-tables-in-sqlite</id>
    <title>🔗 Prefer strict tables in SQLite</title>
    <updated>2026-08-04T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/rewriting-bun-in-rust" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Bun&apos;s evolution from Zig to Rust was not just a coding challenge; it aimed to resolve fundamental stability issues. The article details the ambitious journey of rewriting Bun&apos;s large codebase to enhance memory safety and eliminate bugs inherent in manual memory management. Originally a port from Go to Zig, the developer recognized weaknesses in Zig&apos;s handling of garbage-collected and manually-managed memory. By leveraging a new model from Anthropic, the team performed an efficient mechanical port to Rust, thus enabling safer code practices through Rust&apos;s strong type system and automatic memory management. The result? A more robust Bun, aiming for long-term stability and performance enhancement without sacrificing its original feature set. It&apos;s a case study in planning, execution, and leveraging AI in software development for increased reliability.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://bun.com/blog/bun-in-rust&quot;&gt;Continue reading on &lt;strong&gt;bun.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/development&quot;&gt;#development&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-08-04T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/rewriting-bun-in-rust</id>
    <title>🔗 Rewriting Bun in Rust</title>
    <updated>2026-08-04T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/ever-run-into-a-postgresql-query-that-you-can-figure-out-what-it-does" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Ever encountered an indecipherable PostgreSQL query that you still need to support? This article outlines how DBeaver&apos;s AI Assistant can demystify complex queries for users. With DBeaver 26.1.2, the AI Assistant analyzes queries and provides understandable explanations. The author illustrates this tool&apos;s efficacy using a sample query that identifies the top 10 most popular films by rental count and total revenue. From common table expressions (CTEs) to subqueries, the breakdown makes clear how the query&apos;s elements interact to produce results, highlighting the AI&apos;s utility when faced with obfuscated SQL. This guide empowers developers and database administrators to tackle intimidating SQL queries with confidence, showcasing an essential feature for anyone working with PostgreSQL.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://stokerpostgresql.blogspot.com/2026/07/ever-run-into-postgresql-query-that-you.html&quot;&gt;Continue reading on &lt;strong&gt;stokerpostgresql.blogspot.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/ai&quot;&gt;#ai&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/postgresql&quot;&gt;#postgresql&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/sql&quot;&gt;#sql&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-04T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/ever-run-into-a-postgresql-query-that-you-can-figure-out-what-it-does</id>
    <title>🔗 Ever run into a PostgreSQL query that you can figure out what it does??</title>
    <updated>2026-08-04T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/adding-2fa-to-phoenix-liveview-with-phx-gen-auth" rel="alternate"/>
    <content type="html">&lt;p&gt;Two-factor authentication (2FA) is a great way to improve account security. This post walks through how to add TOTP-based 2FA to a Phoenix LiveView app using the built-in &lt;code&gt;phx.gen.auth&lt;/code&gt; authentication system.&lt;/p&gt;
&lt;p&gt;We’ll cover:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding required fields and dependencies&lt;/li&gt;
&lt;li&gt;2FA setup flow using LiveView&lt;/li&gt;
&lt;li&gt;TOTP challenge during login&lt;/li&gt;
&lt;li&gt;LiveView-specific login handling&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Add dependencies&lt;/h2&gt;
&lt;p&gt;Add the following libraries to &lt;code&gt;mix.exs&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;l-function&quot;&gt;deps&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;l-punctuation-bracket&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;    &lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:nimble_totp&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;~&gt; 1.0&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;    &lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:qr_code&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;~&gt; 2.2&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;  &lt;span class=&quot;l-punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run:&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;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-function-call&quot;&gt;mix&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;deps.get&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Extend the user schema&lt;/h2&gt;
&lt;p&gt;Add two fields to your &lt;code&gt;users&lt;/code&gt; table:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-comment&quot;&gt;# priv/repo/migrations/*_add_totp_to_users.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;l-function-call&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;l-function-call&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;  &lt;span class=&quot;l-function-call&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:totp_secret&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:string&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;l-function-call&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:totp_confirmed_at&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:utc_datetime&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;&lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Migrate:&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;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-function-call&quot;&gt;mix&lt;/span&gt; &lt;span class=&quot;l-variable-parameter&quot;&gt;ecto.migrate&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your user schema:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-function-call&quot;&gt;schema&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;users&quot;&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;l-function-call&quot;&gt;field&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:totp_secret&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:string&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;  &lt;span class=&quot;l-function-call&quot;&gt;field&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:totp_confirmed_at&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:utc_datetime&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;l-comment&quot;&gt;# ...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;&lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ensure your &lt;code&gt;changeset/2&lt;/code&gt; casts these fields:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;l-function&quot;&gt;changeset&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;  &lt;span class=&quot;l-operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;l-function-call&quot;&gt;cast&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:totp_secret&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:totp_confirmed_at&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;l-operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;l-function-call&quot;&gt;validate_required&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;&lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Add 2FA setup LiveView&lt;/h2&gt;
&lt;p&gt;Create a new LiveView at &lt;code&gt;/settings/two_factor&lt;/code&gt; that generates a secret, renders a QR code, and lets the user enter their TOTP code.&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-keyword-function&quot;&gt;defmodule&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;MyAppWeb.TwoFactorSetupLive&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;MyAppWeb&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:live_view&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;NimbleTOTP&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;QRCode&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;MyApp.Accounts&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;7&quot;&gt;  &lt;span class=&quot;l-keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;l-function&quot;&gt;mount&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-comment&quot;&gt;_params&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-comment&quot;&gt;_session&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;assigns&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;current_user&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;10&quot;&gt;    &lt;span class=&quot;l-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;totp_secret&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;11&quot;&gt;      &lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-function-call&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;setup?: &lt;/span&gt;&lt;span class=&quot;l-boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;12&quot;&gt;    &lt;span class=&quot;l-keyword&quot;&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;13&quot;&gt;      &lt;span class=&quot;l-variable&quot;&gt;secret&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;encode32&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-module&quot;&gt;NimbleTOTP&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;14&quot;&gt;      &lt;span class=&quot;l-variable&quot;&gt;uri&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;NimbleTOTP&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;otpauth_uri&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;MyApp:&lt;/span&gt;&lt;span class=&quot;l-string-special&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;l-string-special&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;decode32!&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;issuer: &lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;MyApp&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;15&quot;&gt;      &lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;png&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;QRCode&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:png&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;16&quot;&gt;      &lt;span class=&quot;l-variable&quot;&gt;b64&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;encode64&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;png&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;18&quot;&gt;      &lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;19&quot;&gt;       &lt;span class=&quot;l-function-call&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;20&quot;&gt;         &lt;span class=&quot;l-string-special-symbol&quot;&gt;setup?: &lt;/span&gt;&lt;span class=&quot;l-boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;21&quot;&gt;         &lt;span class=&quot;l-string-special-symbol&quot;&gt;secret: &lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;22&quot;&gt;         &lt;span class=&quot;l-string-special-symbol&quot;&gt;qr_code: &lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;data:image/png;base64,&lt;/span&gt;&lt;span class=&quot;l-string-special&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;b64&lt;/span&gt;&lt;span class=&quot;l-string-special&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;23&quot;&gt;         &lt;span class=&quot;l-string-special-symbol&quot;&gt;code: &lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;24&quot;&gt;         &lt;span class=&quot;l-string-special-symbol&quot;&gt;error: &lt;/span&gt;&lt;span class=&quot;l-constant-builtin&quot;&gt;nil&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;25&quot;&gt;       &lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;26&quot;&gt;    &lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;27&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;28&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;29&quot;&gt;  &lt;span class=&quot;l-keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;l-function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;verify&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;code&quot;&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;30&quot;&gt;    &lt;span class=&quot;l-variable&quot;&gt;secret&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;decode32!&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;assigns&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;31&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;32&quot;&gt;    &lt;span class=&quot;l-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;NimbleTOTP&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;valid?&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;33&quot;&gt;      &lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-comment&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;34&quot;&gt;        &lt;span class=&quot;l-module&quot;&gt;Accounts&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;update_user&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;assigns&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;35&quot;&gt;          &lt;span class=&quot;l-string-special-symbol&quot;&gt;totp_secret: &lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;assigns&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;36&quot;&gt;          &lt;span class=&quot;l-string-special-symbol&quot;&gt;totp_confirmed_at: &lt;/span&gt;&lt;span class=&quot;l-module&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;utc_now&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;37&quot;&gt;        &lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;38&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;39&quot;&gt;      &lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:noreply&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;40&quot;&gt;       &lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;41&quot;&gt;       &lt;span class=&quot;l-operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;l-function-call&quot;&gt;put_flash&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:info&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;2FA enabled.&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;42&quot;&gt;       &lt;span class=&quot;l-operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;l-function-call&quot;&gt;push_redirect&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;/settings&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;43&quot;&gt;    &lt;span class=&quot;l-keyword&quot;&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;44&quot;&gt;      &lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:noreply&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-function-call&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;error: &lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;Invalid code&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;45&quot;&gt;    &lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;46&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;47&quot;&gt;&lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-heex&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;!--&lt;/span&gt;&lt;span class=&quot;l-comment&quot;&gt; templates/two_factor_setup_live.html.heex &lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;--&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;l-text&quot;&gt;Two-Factor Authentication&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;/&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;%=&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;l-constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;l-constant&quot;&gt;setup?&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;l-tag-delimiter&quot;&gt;%&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;  &lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;l-text&quot;&gt;Scan this QR code in your authenticator app:&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;/&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;6&quot;&gt;  &lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;l-tag-attribute&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;l-constant&quot;&gt;qr_code&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;l-tag-delimiter&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;8&quot;&gt;  &lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;form&lt;/span&gt; &lt;span class=&quot;l-tag-attribute&quot;&gt;phx-submit&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;verify&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;9&quot;&gt;    &lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;l-text&quot;&gt;Code:&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;/&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;10&quot;&gt;    &lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;l-tag-attribute&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;l-tag-attribute&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;l-tag-delimiter&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;11&quot;&gt;    &lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;l-text&quot;&gt;Verify&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;/&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;12&quot;&gt;  &lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;/&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;14&quot;&gt;  &lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;%=&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;l-constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;l-constant&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;do: &lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;content_tag&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:p&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;l-constant&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;class: &lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;text-red-500&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt; %&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;15&quot;&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;%&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt; %&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;16&quot;&gt;  &lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;l-text&quot;&gt;2FA is already enabled.&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;/&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;17&quot;&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;%&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt; %&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Add a TOTP challenge LiveView&lt;/h2&gt;
&lt;p&gt;When a user logs in with 2FA enabled, redirect them to a LiveView at &lt;code&gt;/two_factor&lt;/code&gt; to enter their code.&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-keyword-function&quot;&gt;defmodule&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;MyAppWeb.TwoFactorChallengeLive&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;MyAppWeb&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;:live_view&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;MyApp.Accounts&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;NimbleTOTP&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;MyAppWeb.UserAuth&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;7&quot;&gt;  &lt;span class=&quot;l-keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;l-function&quot;&gt;mount&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-comment&quot;&gt;_params&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;pending_user_id&quot;&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-function-call&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;user: &lt;/span&gt;&lt;span class=&quot;l-module&quot;&gt;Accounts&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;get_user!&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;code: &lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;error: &lt;/span&gt;&lt;span class=&quot;l-constant-builtin&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;9&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;11&quot;&gt;  &lt;span class=&quot;l-keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;l-function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;verify&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;code&quot;&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;12&quot;&gt;    &lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;assigns&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;14&quot;&gt;    &lt;span class=&quot;l-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;NimbleTOTP&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;valid?&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-module&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;decode32!&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;totp_secret&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;15&quot;&gt;      &lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:noreply&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;16&quot;&gt;       &lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;17&quot;&gt;       &lt;span class=&quot;l-operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;l-function-call&quot;&gt;put_flash&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:info&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string&quot;&gt;&quot;Logged in with 2FA.&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;18&quot;&gt;       &lt;span class=&quot;l-operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;UserAuth&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;log_in_user&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;19&quot;&gt;    &lt;span class=&quot;l-keyword&quot;&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;20&quot;&gt;      &lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:noreply&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-function-call&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;error: &lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;Invalid code&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;21&quot;&gt;    &lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;22&quot;&gt;  &lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;23&quot;&gt;&lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-heex&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;!--&lt;/span&gt;&lt;span class=&quot;l-comment&quot;&gt; templates/two_factor_challenge_live.html.heex &lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;--&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;l-text&quot;&gt;Enter your 2FA code&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;/&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;form&lt;/span&gt; &lt;span class=&quot;l-tag-attribute&quot;&gt;phx-submit&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;verify&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;  &lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;l-tag-attribute&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;l-tag-attribute&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;l-tag-delimiter&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;6&quot;&gt;  &lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;l-text&quot;&gt;Verify&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;/&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;7&quot;&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;/&lt;/span&gt;&lt;span class=&quot;l-tag&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;9&quot;&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt;&lt;%=&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;l-constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;l-constant&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;do: &lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;content_tag&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:p&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;l-constant&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-string-special-symbol&quot;&gt;class: &lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;text-red-500&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;l-tag-delimiter&quot;&gt; %&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Update login logic&lt;/h2&gt;
&lt;p&gt;After validating the user’s password (in your LiveView or controller), check if TOTP is enabled:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;totp_confirmed_at&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;  &lt;span class=&quot;l-operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;Phoenix.LiveView&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;put_session&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:pending_user_id&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;l-operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;Phoenix.LiveView&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;/two_factor&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;&lt;span class=&quot;l-keyword&quot;&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;6&quot;&gt;  &lt;span class=&quot;l-module&quot;&gt;UserAuth&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;log_in_user&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;7&quot;&gt;&lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In controllers, you’d use &lt;code&gt;put_session(conn, ...)&lt;/code&gt; and &lt;code&gt;redirect(conn, ...)&lt;/code&gt; instead.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Update &lt;code&gt;UserAuth&lt;/code&gt; helper&lt;/h2&gt;
&lt;p&gt;Make sure your &lt;code&gt;UserAuth&lt;/code&gt; module has a LiveView-compatible &lt;code&gt;log_in_user/3&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;l-keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;l-function&quot;&gt;log_in_user&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-comment&quot;&gt;_params&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;\\&lt;/span&gt; &lt;span class=&quot;l-punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;l-keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;l-variable&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;l-operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;MyApp.Accounts&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;generate_user_session_token&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;l-variable&quot;&gt;socket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;5&quot;&gt;  &lt;span class=&quot;l-operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;Phoenix.LiveView&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;put_session&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;:user_token&lt;/span&gt;&lt;span class=&quot;l-punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;l-variable&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;6&quot;&gt;  &lt;span class=&quot;l-operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;l-module&quot;&gt;Phoenix.LiveView&lt;/span&gt;&lt;span class=&quot;l-operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;l-function-call&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l-string-special-symbol&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;l-string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;l-punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;l-line&quot; data-line=&quot;7&quot;&gt;&lt;span class=&quot;l-keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Done&lt;/h2&gt;
&lt;p&gt;You now have a working TOTP 2FA flow in Phoenix LiveView:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Users opt in to 2FA in their settings&lt;/li&gt;
&lt;li&gt;A QR code is shown and confirmed&lt;/li&gt;
&lt;li&gt;2FA is required on next login&lt;/li&gt;
&lt;li&gt;All handled with clean LiveView flows&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From here, you can extend the system with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Disabling 2FA&lt;/li&gt;
&lt;li&gt;Backup codes&lt;/li&gt;
&lt;li&gt;Remembering trusted devices&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let me know if you&apos;d like a follow-up on any of those features.&lt;/p&gt;&lt;p&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;a href=&quot;https://www.yellowduck.be/tags/auth&quot;&gt;#auth&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-03T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/adding-2fa-to-phoenix-liveview-with-phx-gen-auth</id>
    <title>🐥 Adding 2FA to Phoenix LiveView with phx.gen.auth</title>
    <updated>2026-08-03T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/more-shell-patterns-i-use-weekly" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;This article shares useful Bash patterns and techniques that boost productivity for everyday tasks. The author highlights various methods like using &lt;code&gt;sed&lt;/code&gt; and &lt;code&gt;xargs&lt;/code&gt; for find-and-replace operations, employing &lt;code&gt;while&lt;/code&gt; loops until success, and parallelizing commands with &lt;code&gt;wait&lt;/code&gt;. The discussion emphasizes the importance of clean output and error handling, using temporary directories for logs with &lt;code&gt;mktemp&lt;/code&gt;, and enhancing user legibility with color-coded output. The author also advocates for efficient file manipulation using shell constructs, recommending &lt;code&gt;awk&lt;/code&gt; for filtering data instead of &lt;code&gt;grep&lt;/code&gt;. These patterns, though simple, can significantly streamline workflows, especially for those working with commands regularly.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://will-keleher.com/posts/more-shell-patterns-i-use-weekly/&quot;&gt;Continue reading on &lt;strong&gt;will-keleher.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;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;/p&gt;</content>
    <published>2026-08-03T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/more-shell-patterns-i-use-weekly</id>
    <title>🔗 More shell patterns I use weekly</title>
    <updated>2026-08-03T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/why-backlogs-are-harmful-why-they-never-shrink-and-what-to-do-instead" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Backlogs never shrink, and their existence often leads to unproductive practices. When teams rely on them, they merely accumulate tasks that distract from prioritizing what truly matters. The article points out that important tasks rarely enter the backlog as teams instinctively know what needs immediate attention. This misalignment causes waste and elongates cycle times, as tasks languish unattended. A well-managed product team can avoid the pitfalls of backlogs by employing a high-level roadmap and maintaining close communication with engineers. In practice, this means regularly revisiting goals, effectively filtering bugs, and keeping the focus on immediate tasks. The author argues that instead of allowing unnecessary tasks to clutter workflows, it’s essential to be ruthless in prioritization and decision-making to enhance productivity and strategic alignment.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://www.lucasfcosta.com/blog/backlogs-are-useless&quot;&gt;Continue reading on &lt;strong&gt;www.lucasfcosta.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;/p&gt;</content>
    <published>2026-08-03T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/why-backlogs-are-harmful-why-they-never-shrink-and-what-to-do-instead</id>
    <title>🔗 Why backlogs are harmful, why they never shrink, and what to do instead</title>
    <updated>2026-08-03T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/the-laravel-scheduler-under-the-hood" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Laravel&apos;s Scheduler features an API that simplifies task scheduling, but its underlying mechanics are crucial to understand. When cron triggers &lt;code&gt;php artisan schedule:run&lt;/code&gt;, Laravel takes over to determine which events are due. This article dives deep into the Laravel Scheduler, explaining how it processes scheduled tasks, from command definitions to execution. It clarifies concepts like &lt;code&gt;onOneServer()&lt;/code&gt; and &lt;code&gt;withoutOverlapping()&lt;/code&gt;, which are essential for managing concurrent tasks. Readers learn how Laravel creates event objects from command definitions and how frequency methods adjust cron expressions. The article emphasizes the distinctions between the Scheduler and the Queue system, underpinning the operational design decisions that make task scheduling effective in production environments. Armed with this insight, Laravel developers can better leverage the Scheduler for reliable task execution.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://wendelladriel.com/blog/the-laravel-scheduler-under-the-hood&quot;&gt;Continue reading on &lt;strong&gt;wendelladriel.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/best-practice&quot;&gt;#best-practice&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-08-02T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/the-laravel-scheduler-under-the-hood</id>
    <title>🔗 The Laravel Scheduler Under the Hood</title>
    <updated>2026-08-02T17:00:00Z</updated>
  </entry>
</feed>