<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[packagemain.tech]]></title><description><![CDATA[Hands-on, practical and real-world tutorials that you can use to build your software development skills. Main topics: Backend, Databases, Software Architecture, DevOps, Distributed Systems, APIs.]]></description><link>https://packagemain.tech</link><image><url>https://substackcdn.com/image/fetch/$s_!ya8w!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42332f2e-7531-44b1-920c-bba7831fcdbe_777x777.png</url><title>packagemain.tech</title><link>https://packagemain.tech</link></image><generator>Substack</generator><lastBuildDate>Wed, 29 Jul 2026 09:00:30 GMT</lastBuildDate><atom:link href="https://packagemain.tech/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Aliaksandr Pliutau]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[packagemain@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[packagemain@substack.com]]></itunes:email><itunes:name><![CDATA[Alex Pliutau]]></itunes:name></itunes:owner><itunes:author><![CDATA[Alex Pliutau]]></itunes:author><googleplay:owner><![CDATA[packagemain@substack.com]]></googleplay:owner><googleplay:email><![CDATA[packagemain@substack.com]]></googleplay:email><googleplay:author><![CDATA[Alex Pliutau]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Summer Offer - 50% off]]></title><description><![CDATA[Hi dear readers,]]></description><link>https://packagemain.tech/p/summer-offer-50-off</link><guid isPermaLink="false">https://packagemain.tech/p/summer-offer-50-off</guid><dc:creator><![CDATA[Alex Pliutau]]></dc:creator><pubDate>Sat, 25 Jul 2026 14:45:35 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f12892d9-4c0b-442b-a165-477227350784_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi dear readers,</p><p>if you&#8217;ve been thinking about subscribing, this is the best time to join. We wanted to give a 50% discount until the end of summer.</p><p>You may ask, why not all articles are free? We start publishing them as free and limit the older ones so LLMs don&#8217;t train on them such easily.</p><p>Our goal remains the same: publish practical, high-quality engineering content that helps you become a better software engineer.</p><p>As a paid subscriber, you&#8217;ll get:</p><ul><li><p>Two hands-on technical articles every month.</p></li><li><p>Full access to our entire archive of paid content.</p></li><li><p>Access to our private Discord, where you can chat directly with the writers.</p></li><li><p>The opportunity to suggest topics and help shape what we publish next.</p></li></ul><p>If that sounds like something you&#8217;d enjoy, we&#8217;d love to have you in the community.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://packagemain.tech/cf00f4c3&quot;,&quot;text&quot;:&quot;JOIN&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://packagemain.tech/cf00f4c3"><span>JOIN</span></a></p><p>Best,</p><p>Alex, Julien</p>]]></content:encoded></item><item><title><![CDATA[Building a pkg.go.dev TUI explorer]]></title><description><![CDATA[The new API finally makes it practical to browse Go documentation entirely from anywhere.]]></description><link>https://packagemain.tech/p/building-a-pkggodev-tui-explorer</link><guid isPermaLink="false">https://packagemain.tech/p/building-a-pkggodev-tui-explorer</guid><dc:creator><![CDATA[Alex Pliutau]]></dc:creator><pubDate>Mon, 20 Jul 2026 20:00:20 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c82286cb-67b4-4fbd-abba-432207a19180_2400x1350.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For years, <strong><a href="https://pkg.go.dev">pkg.go.dev</a></strong> has been the central place to discover Go packages.</p><p>I genuinely like the project. The interface is clean, fast, and consistent. Unlike ecosystems such as npm, package authors don&#8217;t need to explicitly publish documentation anywhere&#8212;if your module is available through the Go module ecosystem, it&#8217;s already indexed.</p><p>The documentation is generated directly from source code downloaded via the <a href="https://proxy.golang.org/">Go Module Mirror</a>, making it one of the most seamless documentation systems around.</p><p>For example, I maintain a PayPal SDK for Go, and it&#8217;s automatically available on pkg.go.dev without any extra publishing step.</p><p><a href="https://pkg.go.dev/github.com/plutov/paypal/v4">https://pkg.go.dev/github.com/plutov/paypal/v4</a></p><p>Recently, the Go team took this one step further by launching the <strong><a href="https://go.dev/blog/pkgsite-api">pkg.go.dev API</a></strong>.</p><p>Instead of scraping HTML or reverse-engineering the website, we now have direct programmatic access to package metadata, module versions, symbols, vulnerabilities, import relationships, and much more.</p><p>Even better, the API is published with an official OpenAPI specification.</p><p><a href="https://pkg.go.dev/v1beta/openapi.yaml">https://pkg.go.dev/v1beta/openapi.yaml</a></p><p>That seemingly small detail changes everything. It means generating a Go client becomes almost effortless.</p><h2>Why I Care</h2><p>I spend most of my day inside Neovim.</p><p>Every time I need to open a browser just to look up package documentation, it breaks my flow. Switching contexts sounds insignificant until you do it dozens of times every day.</p><p>The new API finally makes it practical to browse Go documentation entirely from the terminal.</p><p>So I decided to build a small TUI (terminal UI) that lets me:</p><ul><li><p>search packages</p></li><li><p>browse module versions</p></li><li><p>inspect exported symbols</p></li><li><p>extend it later with vulnerabilities, dependency information, and more</p></li></ul><p>all without leaving the terminal.</p><p>Here is what&#8217;s we&#8217;re going to build:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TlHZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd313859b-5750-4d44-be3e-012d1ef4551f_810x513.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TlHZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd313859b-5750-4d44-be3e-012d1ef4551f_810x513.png 424w, https://substackcdn.com/image/fetch/$s_!TlHZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd313859b-5750-4d44-be3e-012d1ef4551f_810x513.png 848w, https://substackcdn.com/image/fetch/$s_!TlHZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd313859b-5750-4d44-be3e-012d1ef4551f_810x513.png 1272w, https://substackcdn.com/image/fetch/$s_!TlHZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd313859b-5750-4d44-be3e-012d1ef4551f_810x513.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TlHZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd313859b-5750-4d44-be3e-012d1ef4551f_810x513.png" width="810" height="513" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d313859b-5750-4d44-be3e-012d1ef4551f_810x513.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:513,&quot;width&quot;:810,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:78945,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/207824579?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd313859b-5750-4d44-be3e-012d1ef4551f_810x513.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TlHZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd313859b-5750-4d44-be3e-012d1ef4551f_810x513.png 424w, https://substackcdn.com/image/fetch/$s_!TlHZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd313859b-5750-4d44-be3e-012d1ef4551f_810x513.png 848w, https://substackcdn.com/image/fetch/$s_!TlHZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd313859b-5750-4d44-be3e-012d1ef4551f_810x513.png 1272w, https://substackcdn.com/image/fetch/$s_!TlHZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd313859b-5750-4d44-be3e-012d1ef4551f_810x513.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Sponsor</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://coderabbit.link/alex-cli" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!D6hO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 424w, https://substackcdn.com/image/fetch/$s_!D6hO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 848w, https://substackcdn.com/image/fetch/$s_!D6hO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 1272w, https://substackcdn.com/image/fetch/$s_!D6hO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!D6hO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png" width="1456" height="827" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:827,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:&quot;https://coderabbit.link/alex-cli&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!D6hO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 424w, https://substackcdn.com/image/fetch/$s_!D6hO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 848w, https://substackcdn.com/image/fetch/$s_!D6hO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 1272w, https://substackcdn.com/image/fetch/$s_!D6hO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 1456w" sizes="100vw" loading="lazy" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>AI writes more code than ever. Reviewing it shouldn&#8217;t mean scrolling forty files in alphabetical order.</p><p><a href="https://coderabbit.link/alex-cli">CodeRabbit Review</a> reorganizes any pull request from a flat file list into a structured, layer-by-layer walkthrough - the logical reading order of the change, not the order your platform happens to sort it. Every range gets its own plain-language summary, with sequence diagrams, state machines, and ERDs generated inline wherever a visual earns its place.</p><p>Cohorts group related files and chunks so you review one idea at a time. Layers order them so foundational changes - data shapes, contracts - come before the code that depends on them. Code Peek lets you click any variable, function, class or type to see its definition and usages without leaving the tab, while Semantic Diff view cuts past formatting noise to show what actually changed.</p><p>Open it straight from the Review Change Stack button in the PR Walkthrough. Navigate cohorts and layers from the keyboard, comment against exact line ranges and submit native reviews, comments and approvals post back to GitHub or GitLab right where your team expects them.</p><p>In the early access, available for free to everyone.</p><p>From the team that pioneered AI code reviews. 2M reviews every week. 6M repos. 15K customers.The review interface built for the way modern PRs are actually written.</p><p><a href="https://coderabbit.link/alex-cli">Review your next PR with CodeRabbit Review Today</a></p><h2>Exploring the API</h2><p>The API exposes structured metadata through a collection of REST endpoints.</p><p>Some of the most useful ones are:</p><ul><li><p><code>/search</code></p></li><li><p><code>/package</code></p></li><li><p><code>/module</code></p></li><li><p><code>/versions</code></p></li><li><p><code>/symbols</code></p></li><li><p><code>/imported-by</code></p></li><li><p><code>/vulns</code></p></li></ul><p>Everything is read-only and designed to be cache-friendly.</p><p>For example, retrieving metadata for the <code>cmp</code> package is as simple as:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">curl https://pkg.go.dev/v1beta/package/github.com/google/go-cmp/cmp | jq</code></pre></div><p>The response includes information such as:</p><ul><li><p>module path</p></li><li><p>package path</p></li><li><p>version</p></li><li><p>synopsis</p></li><li><p>redistributable status</p></li></ul><p>The important part isn&#8217;t the JSON itself.</p><p>The important part is that we can now build tools on top of Go&#8217;s package ecosystem without scraping a single HTML page.</p><h2>Generating an API Client</h2><p>Since the API publishes an OpenAPI schema, generating a Go SDK is straightforward using <strong>oapi-codegen</strong> by Jamie Tanna.</p><p><a href="https://github.com/oapi-codegen/oapi-codegen">https://github.com/oapi-codegen/oapi-codegen</a></p><p>I&#8217;ve previously made a <a href="https://youtu.be/87au30fl5e4?si=38yjFVNga6yf85-4">video about </a><code>oapi-codegen</code>, so this project was a perfect excuse to use it again.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest

oapi-codegen \
  -generate types,client \
  -package pkgsiteapi \
  pkgsiteapi/openapi.yaml &gt; pkgsiteapi/client.gen.go</code></pre></div><p>There was one small catch.</p><p>At the time I built this project, generating directly from the published OpenAPI URL didn&#8217;t work. The document itself contained a couple of issues:</p><ul><li><p>some <code>{path}</code> endpoints were missing their corresponding path parameter definitions</p></li><li><p>one schema reference was invalid<br></p></li></ul><p>My workaround was simple: download the specification, fix the issues locally, and generate the client from that file instead.</p><p>I also noticed that some response types aren&#8217;t fully modeled. For example, the <code>/search</code> endpoint doesn&#8217;t map directly to a dedicated <code>SearchResult</code> type, so I had to extend the generated models myself.</p><p>Nothing impossible, but there is definitely room for improvement.</p><h2>Building the TUI</h2><p>For the interface I used <strong><a href="https://github.com/charmbracelet/bubbletea">Bubble Tea</a></strong>, which has become my default choice for terminal applications.</p><p>The application itself is intentionally minimal.</p><p>There are three areas of focus:</p><ul><li><p>Search input</p></li><li><p>Search results</p></li><li><p>Package details</p></li></ul><p>The model keeps track of the currently selected package along with the API responses.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go">ype Model struct {
    client *pkgsiteapi.ClientWithResponses

    input    textinput.Model
    viewport viewport.Model

    results  []pkgsiteapi.SearchResult
    currItem *pkgsiteapi.SearchResult

    versions *pkgsiteapi.PaginatedResponse
    symbols  *pkgsiteapi.PackageSymbols

    loading bool
    focus   focusMode
}</code></pre></div><p>Bubble Tea&#8217;s architecture fits this kind of application nicely.</p><p>The UI reacts to messages:</p><ul><li><p>a search completes</p></li><li><p>package details arrive</p></li><li><p>an error occurs<br></p></li></ul><p>The <code>Update()</code> function becomes the event loop that coordinates everything, while the API calls live separately in <code>commands.go</code>.</p><p>The result is a clean separation between:</p><ul><li><p>state</p></li><li><p>networking</p></li><li><p>rendering<br></p></li></ul><p>which makes the application easy to extend later.</p><p>Btw, there is also a <a href="https://youtu.be/_gzypL-Qv-g?si=_UwzbjfDSzKSJ2gi">video me explaining the details of Bubble Tea</a>.</p><h2>Putting Everything Together</h2><p>The entry point is pleasantly small.</p><p>After creating the generated API client, Bubble Tea takes over.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go">client, err := pkgsiteapi.NewClientWithResponses(apiBaseURL)

if _, err := tea.NewProgram(
    newModel(client),
    tea.WithAltScreen(),
).Run(); err != nil {
    ...
}</code></pre></div><p>Most of the complexity ends up living in the update loop rather than in <code>main.go</code>, which is exactly where it belongs.</p><h2>Where This Can Go Next</h2><p>The current application only scratches the surface of what&#8217;s available.</p><p>Because the API already exposes module versions, symbols, dependency information, and vulnerabilities, it wouldn&#8217;t take much to add features like:</p><ul><li><p>vulnerability browsing</p></li><li><p>dependency trees</p></li><li><p>reverse dependency lookup</p></li><li><p>package bookmarking</p></li><li><p>offline caching</p></li><li><p>fuzzy search</p></li><li><p>documentation previews</p></li></ul><p>The hard part&#8212;accessing structured package data&#8212;is already solved by the Go team.</p><h2>Final Thoughts</h2><p>I think this API is one of the more exciting additions to the Go tooling ecosystem in recent years.</p><p>It removes the need for scraping, provides an official machine-readable interface, and makes building editor plugins, terminal tools, dashboards, and automation dramatically easier.</p><p>I&#8217;m already using my terminal package browser daily, and it saves me from constantly switching to the browser while working in Neovim.</p><p>If you&#8217;re building developer tooling for Go, this API is definitely worth exploring.</p><p>Thanks to the Go team for making it available.</p><h2>Resources</h2><ul><li><p><a href="https://coderabbit.link/alex-cli">CodeRabbit Review</a></p></li><li><p><a href="https://github.com/plutov/packagemain/tree/main/gopkg">Source code for this project</a></p></li><li><p><a href="https://pkg.go.dev">pkg.go.dev</a></p></li><li><p><a href="https://proxy.golang.org/">Go Module Mirror</a></p></li><li><p><a href="https://go.dev/blog/pkgsite-api">Introducing the pkg.go.dev API</a></p></li><li><p><a href="https://pkg.go.dev/v1beta/openapi.yaml">OpenAPI specification</a></p></li><li><p><a href="https://news.ycombinator.com/item?id=48984118">Discuss on Hacker News</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Review the actual change, not the file list]]></title><description><![CDATA[A hands-on look at CodeRabbit on a real, absurdly large feature-removal PR.]]></description><link>https://packagemain.tech/p/review-the-actual-change-not-the</link><guid isPermaLink="false">https://packagemain.tech/p/review-the-actual-change-not-the</guid><dc:creator><![CDATA[Julien Singler]]></dc:creator><pubDate>Fri, 17 Jul 2026 12:23:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!HulP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HulP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HulP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png 424w, https://substackcdn.com/image/fetch/$s_!HulP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png 848w, https://substackcdn.com/image/fetch/$s_!HulP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png 1272w, https://substackcdn.com/image/fetch/$s_!HulP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HulP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2846209,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/207249340?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HulP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png 424w, https://substackcdn.com/image/fetch/$s_!HulP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png 848w, https://substackcdn.com/image/fetch/$s_!HulP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png 1272w, https://substackcdn.com/image/fetch/$s_!HulP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd54a0e6-ac6c-4d08-933d-93ac008d0008_1456x825.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>Sponsored by <strong><a href="https://coderabbit.link/alex-cli">CodeRabbit</a></strong> &#8212; but this is a real PR, on a real codebase, with real findings. I&#8217;m going to show you exactly what the tool caught, what it got wrong, and whether the &#8220;AI reviews your PR&#8221; pitch holds up when the PR is 218 files and minus&#8209;74,096 lines.</p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0Zls!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a6c1d49-ae9d-4165-96a7-23f636f0c810_2048x1163.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0Zls!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a6c1d49-ae9d-4165-96a7-23f636f0c810_2048x1163.png 424w, https://substackcdn.com/image/fetch/$s_!0Zls!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a6c1d49-ae9d-4165-96a7-23f636f0c810_2048x1163.png 848w, https://substackcdn.com/image/fetch/$s_!0Zls!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a6c1d49-ae9d-4165-96a7-23f636f0c810_2048x1163.png 1272w, https://substackcdn.com/image/fetch/$s_!0Zls!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a6c1d49-ae9d-4165-96a7-23f636f0c810_2048x1163.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0Zls!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a6c1d49-ae9d-4165-96a7-23f636f0c810_2048x1163.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a6c1d49-ae9d-4165-96a7-23f636f0c810_2048x1163.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0Zls!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a6c1d49-ae9d-4165-96a7-23f636f0c810_2048x1163.png 424w, https://substackcdn.com/image/fetch/$s_!0Zls!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a6c1d49-ae9d-4165-96a7-23f636f0c810_2048x1163.png 848w, https://substackcdn.com/image/fetch/$s_!0Zls!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a6c1d49-ae9d-4165-96a7-23f636f0c810_2048x1163.png 1272w, https://substackcdn.com/image/fetch/$s_!0Zls!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a6c1d49-ae9d-4165-96a7-23f636f0c810_2048x1163.png 1456w" sizes="100vw"></picture><div></div></div></a></figure></div><p><strong><span>AI writes more code than ever. Reviewing it shouldn&#8217;t mean scrolling forty files in alphabetical order.</span></strong></p><p><span>CodeRabbit Review reorganizes any pull request from a flat file list into a structured, layer-by-layer walkthrough - the logical reading order of the change, not the order your platform happens to sort it. Every range gets its own plain-language summary, with sequence diagrams, state machines, and ERDs generated inline wherever a visual earns its place.</span></p><p><strong><span>Cohorts</span></strong><span> group related files and chunks so you review one idea at a time. </span><strong><span>Layers</span></strong><span> order them so foundational changes - data shapes, contracts - come before the code that depends on them. </span><strong><span>Code Peek</span></strong><span> lets you click any variable, function, class or type to see its definition and usages without leaving the tab, while  </span><strong><span>Semantic Diff</span></strong><span> view cuts past formatting noise to show what actually changed.</span></p><p><span>Open it straight from the Review Change Stack button in the PR Walkthrough. Navigate cohorts and layers from the keyboard, comment against exact line ranges and submit native reviews, comments and approvals post back to GitHub or GitLab right where your team expects them.</span></p><p><em><span>In the early access, available for free to everyone.</span></em></p><p><span>From the team that pioneered AI code reviews. 2M reviews every week. 6M repos. 15K customers.The review interface built for the way modern PRs are actually written.</span></p><p><strong><span>Review your next PR with </span><a href="https://coderabbit.link/alex-cli"><span>CodeRabbit Review</span></a><span> Today</span></strong></p><div><hr></div><h2>The setup: killing a feature that outgrew the game</h2><p>HiddenWars is a browser-based hacking strategy game &#8212; Go + Echo on the backend, Svelte 5 on the frontend, PostgreSQL, Redis. <br>The <strong>Corruption War</strong> was a cooperative map/season meta-game that had accumulated ~74k lines of code and too many design docs. It was time for it to go.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Q5X8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8250e7a4-22cc-4ba3-b3c1-0696429531db_729x161.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Q5X8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8250e7a4-22cc-4ba3-b3c1-0696429531db_729x161.png 424w, https://substackcdn.com/image/fetch/$s_!Q5X8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8250e7a4-22cc-4ba3-b3c1-0696429531db_729x161.png 848w, https://substackcdn.com/image/fetch/$s_!Q5X8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8250e7a4-22cc-4ba3-b3c1-0696429531db_729x161.png 1272w, https://substackcdn.com/image/fetch/$s_!Q5X8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8250e7a4-22cc-4ba3-b3c1-0696429531db_729x161.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Q5X8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8250e7a4-22cc-4ba3-b3c1-0696429531db_729x161.png" width="729" height="161" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8250e7a4-22cc-4ba3-b3c1-0696429531db_729x161.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:161,&quot;width&quot;:729,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31244,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/207249340?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8250e7a4-22cc-4ba3-b3c1-0696429531db_729x161.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Q5X8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8250e7a4-22cc-4ba3-b3c1-0696429531db_729x161.png 424w, https://substackcdn.com/image/fetch/$s_!Q5X8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8250e7a4-22cc-4ba3-b3c1-0696429531db_729x161.png 848w, https://substackcdn.com/image/fetch/$s_!Q5X8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8250e7a4-22cc-4ba3-b3c1-0696429531db_729x161.png 1272w, https://substackcdn.com/image/fetch/$s_!Q5X8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8250e7a4-22cc-4ba3-b3c1-0696429531db_729x161.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><blockquote><p><strong>218 files changed &#183; +1,453 / &#8722;74,096 &#183; 5 phases &#183; one SQL migration</strong></p></blockquote><p>This is the worst-case input for an automated reviewer. Most of the diff is deletions, the codebase has a genuinely confusing naming collision (more on that in a minute), and the &#8220;interesting&#8221; changes are surgical edits buried inside shared files. If a tool can produce a useful review here, it can produce one anywhere.</p><p>I installed CodeRabbit on the repo, pointed it at the PR, set the profile to <strong>Chill</strong> (the balanced middle option &#8212; more on the three profiles later), and let it run.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Fkcu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ff8e9f-bc27-41fa-af58-6b57a5397c15_889x749.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Fkcu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ff8e9f-bc27-41fa-af58-6b57a5397c15_889x749.png 424w, https://substackcdn.com/image/fetch/$s_!Fkcu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ff8e9f-bc27-41fa-af58-6b57a5397c15_889x749.png 848w, https://substackcdn.com/image/fetch/$s_!Fkcu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ff8e9f-bc27-41fa-af58-6b57a5397c15_889x749.png 1272w, https://substackcdn.com/image/fetch/$s_!Fkcu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ff8e9f-bc27-41fa-af58-6b57a5397c15_889x749.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Fkcu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ff8e9f-bc27-41fa-af58-6b57a5397c15_889x749.png" width="889" height="749" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c1ff8e9f-bc27-41fa-af58-6b57a5397c15_889x749.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:749,&quot;width&quot;:889,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:167158,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/207249340?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ff8e9f-bc27-41fa-af58-6b57a5397c15_889x749.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Fkcu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ff8e9f-bc27-41fa-af58-6b57a5397c15_889x749.png 424w, https://substackcdn.com/image/fetch/$s_!Fkcu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ff8e9f-bc27-41fa-af58-6b57a5397c15_889x749.png 848w, https://substackcdn.com/image/fetch/$s_!Fkcu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ff8e9f-bc27-41fa-af58-6b57a5397c15_889x749.png 1272w, https://substackcdn.com/image/fetch/$s_!Fkcu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1ff8e9f-bc27-41fa-af58-6b57a5397c15_889x749.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>What follows is a look at what it caught on the Go specifically, what it got wrong, and whether the &#8220;AI reviews your PR&#8221; pitch holds up when the PR is 218 files.</p><h2>The hard part: a Go interface that mixed two systems</h2><p>The single gnarliest file was <code>backend/internal/db/botnets.go</code>. It defin<code>s a repository interface that he botnet subsystem hangs off:</code></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:&quot;60688240-6c60-4296-835a-d34f2a0f59bd&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go">type BotnetRepository interface {
&#9;ListPlayerNodes(ctx context.Context, playerID string, limit, offset int, filter NodeListFilter) ([]models.PlayerNode, int, error)
&#9;// ... healthy, degraded, corrupted node management ...

&#9;// Siphon-scoped locking: pins draining nodes to a specific session so
&#9;// concurrent siphons keep separate draining pools.
&#9;LockNodesForSiphon(ctx context.Context, playerID, slug string, count int, sessionID string) (int, error)
&#9;UnlockNodesBySiphonSession(ctx context.Context, playerID, sessionID string) error

&#9;// War-op-scoped locking: pins healthy/degraded nodes of slug to a hack op as
&#9;// 'war_committed' (ownership via war_op_id). Returns the locked node IDs.
&#9;LockNodesForWarOp(ctx context.Context, playerID, slug string, qty int, opID string) ([]string, error)

&#9;// UnlockWarOpNodes reverts the player's surviving war_committed nodes for opID ...
&#9;UnlockWarOpNodes(ctx context.Context, playerID, opID string) error

&#9;// LiveOpComposition returns the current surviving committed composition per
&#9;// player for an op: counts only nodes still state='war_committed'.
&#9;LiveOpComposition(ctx context.Context, opID string) (map[string]models.NodesComposition, error)
&#9;LiveOpDegraded(ctx context.Context, opID string) (map[string]map[string]int, error)
&#9;LiveOpAbsorbers(ctx context.Context, opID string) (map[string]map[string][]models.PlayerNode, error)

&#9;// DegradeWarOpNodesTracked damages up to maxCount of the player's war_committed
&#9;// nodes of slug pinned to opID, using the hack damage model ...
&#9;DegradeWarOpNodesTracked(ctx context.Context, playerID, opID, slug string, maxCount int, damage, corruptThreshold float64) (int, []models.NodeDamageEntry, error)

&#9;// ... raid, bounty, siphon, mining, signature counts ...
}</code></pre></div><p>The trap is the word <strong>corruption</strong>. This interface, and this whole file, contains *two unrelated mechanics*:</p><p>- <strong>The Corruption War</strong> &#8212; the `WarOp` / `LiveOp` methods above, the `war_committed` node state. This is what I&#8217;m deleting.</p><p>- <strong>Node corruption</strong> &#8212; `corrupted` nodes, corruption *spread*, the `corruption_immunity` buff. A separate botnet maintenance mechanic that stays.</p><p>A reviewer &#8212; human or AI &#8212; that confuses those two will either delete the wrong code or drown you in false &#8220;you&#8217;re removing the wrong thing!&#8221; comments. I was specifically watching for how CodeRabbit handled it.</p><h2>The Go that needed surgery, not deletion</h2><p>The war methods came out cleanly &#8212; eight method signatures deleted from the interface, eight implementations deleted below. The subtle work was the *other* Go in the file: methods that stay, but whose raw SQL references war states inline. Take `Se<code>verNodes`:</code></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go">func (b *Botnets) SeverNodes(ctx context.Context, playerID string, nodeIDs []string) (int, error) {
&#9;if len(nodeIDs) == 0 {
&#9;&#9;return 0, nil
&#9;}
&#9;conn := b.Acquire()
&#9;defer conn.Release()

&#9;rows, err := conn.Query(ctx, `
&#9;&#9;DELETE FROM player_nodes
&#9;&#9;WHERE id = ANY($1)
&#9;&#9;  AND botnet_id = (SELECT id FROM botnets WHERE player_id = $2)
&#9;&#9;  AND state NOT IN ('locked', 'committed', 'raid_committed', 'war_committed', 'region_recon',
&#9;&#9;                    'boss_committed', 'hashing', 'draining', 'recompiling')
&#9;&#9;RETURNING id
&#9;`, nodeIDs, playerID)
&#9;// ...
}</code></pre></div><p><code>war_committed</code> and <code>region_recon</code> in that <code>NOT IN</code> list are war coupling &#8212; they have to come out. But <code>&#8217;locked&#8217;</code>, <code>&#8217;committed&#8217;, &#8217;raid_committed&#8217;</code>, <code>&#8217;corrupted&#8217;</code> (further up) all stay, because those are raid/bounty/node-corruption mechanics. This is raw SQL *as a Go string literal*: your compiler will not help you. There is no type checker that catches a stray <code>&#8217;war_committed&#8217;</code> left in a query. This is exactly where an AI reviewer earns its keep &#8212; if it can read the SQL.</p><p>CodeRabbit did. It never flagged the legitimate states, and it correctly identified every war-coupled string across the file. The naming collision it had to reason through: *this <code>corrupted</code> is the keep-mechanic, that <code>war_committed</code> is the delete-mechanic.* It inferred the distinction from context. That&#8217;s the moment I stopped thinking of it as a linter.</p><h2>The same pattern, in a 60-line reaper query</h2><p>Where it got genuinely impressive was <code>ReleaseOrphanedLockedNodes</code> &#8212; a background reaper that&#8217;s one big CTE with a <code>UNION</code> per node state. Each arm reaps nodes stuck in a transient state (locked, committed, hashing, draining) when their owning op/session has vanished. The war removal meant: delete the <code>war_committed</code> arm entirely, but leave the raid/bounty/siphon/decryption arms alone. The surviving query looks like this (trimmed for length):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go">func (b *Botnets) ReleaseOrphanedLockedNodes(ctx context.Context) (int64, error) {
&#9;conn := b.Acquire()
&#9;defer conn.Release()
&#9;tag, err := conn.Exec(ctx, `
&#9;&#9;WITH orphaned AS (
&#9;&#9;&#9;-- committed nodes whose raid is completed
&#9;&#9;&#9;SELECT pn.id FROM player_nodes pn
&#9;&#9;&#9;JOIN botnets bot ON bot.id = pn.botnet_id
&#9;&#9;&#9;WHERE pn.state IN ('committed', 'raid_committed')
&#9;&#9;&#9;  AND pn.locked_at IS NOT NULL
&#9;&#9;&#9;  AND NOT EXISTS (
&#9;&#9;&#9;    SELECT 1 FROM raid_registrations rr
&#9;&#9;&#9;    JOIN raids r ON r.id = rr.raid_id
&#9;&#9;&#9;    WHERE rr.player_id = bot.player_id AND r.completed_at IS NULL
&#9;&#9;&#9;  )
&#9;&#9;&#9;UNION
&#9;&#9;&#9;-- locked nodes with no live bounty hunt ...
&#9;&#9;&#9;UNION
&#9;&#9;&#9;-- hashing nodes with no active decryption event ...
&#9;&#9;&#9;UNION
&#9;&#9;&#9;-- draining nodes whose owning session ended
&#9;&#9;&#9;SELECT pn.id FROM player_nodes pn
&#9;&#9;&#9;JOIN botnets bot ON bot.id = pn.botnet_id
&#9;&#9;&#9;WHERE pn.state = 'draining'
&#9;&#9;&#9;  AND pn.locked_at IS NOT NULL
&#9;&#9;&#9;  AND NOT EXISTS (
&#9;&#9;&#9;    SELECT 1 FROM siphon_sessions ss
&#9;&#9;&#9;    WHERE ss.id = pn.siphon_session_id AND ss.status = 'active'
&#9;&#9;&#9;  )
&#9;&#9;)
&#9;&#9;UPDATE player_nodes
&#9;&#9;SET state = CASE WHEN health &lt; 70 THEN 'degraded' ELSE 'healthy' END,
&#9;&#9;    locked_at = NULL
&#9;&#9;WHERE id IN (SELECT id FROM orphaned)
&#9;`)
&#9;// ...
}</code></pre></div><p>There *used to be* a fifth arm here &#8212; a <code>war_committed</code> reaper that found nodes whose war op had resolved. Deleting it is trivial. The hard part is being confident the remaining four arms don&#8217;t have a hidden war dependency. CodeRabbit read the query and was satisfied that they didn&#8217;t. A human reviewer would have to do the same read; CodeRabbit did it in the time it took me to refill my coffee.</p><h2>The SQL-in-Go the AI actually flagged</h2><p>CodeRabbit did have an opinion about this file &#8212; a <code>Major</code> on the draining arm of that reaper. The bot noticed that when the reaper resets an orphaned draining node, it clears <code>state</code> and <code>locked_at</code> but leaves <code>siphon_session_id</code> dangling:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">UPDATE player_nodes
SET state = CASE WHEN health &lt; 70 THEN 'degraded' ELSE 'healthy' END,
    locked_at = NULL
WHERE id IN (SELECT id FROM orphaned)</code></pre></div><p>Its suggested fix:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql"> UPDATE player_nodes
 SET state = CASE WHEN health &lt; 70 THEN 'degraded' ELSE 'healthy' END,
    locked_at = NULL,
    siphon_session_id = NULL
WHERE id IN (SELECT id FROM orphaned)</code></pre></div><p>This is a *real* observation about state hygiene in a reaper query. I ultimately didn&#8217;t take it &#8212; and here&#8217;s the honest part of an honest review.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Gfly!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0c0af0d-57f2-4f75-ad60-9fd0b50a4a8c_781x658.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Gfly!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0c0af0d-57f2-4f75-ad60-9fd0b50a4a8c_781x658.png 424w, https://substackcdn.com/image/fetch/$s_!Gfly!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0c0af0d-57f2-4f75-ad60-9fd0b50a4a8c_781x658.png 848w, https://substackcdn.com/image/fetch/$s_!Gfly!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0c0af0d-57f2-4f75-ad60-9fd0b50a4a8c_781x658.png 1272w, https://substackcdn.com/image/fetch/$s_!Gfly!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0c0af0d-57f2-4f75-ad60-9fd0b50a4a8c_781x658.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Gfly!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0c0af0d-57f2-4f75-ad60-9fd0b50a4a8c_781x658.png" width="781" height="658" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a0c0af0d-57f2-4f75-ad60-9fd0b50a4a8c_781x658.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:658,&quot;width&quot;:781,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:99486,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/207249340?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0c0af0d-57f2-4f75-ad60-9fd0b50a4a8c_781x658.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Gfly!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0c0af0d-57f2-4f75-ad60-9fd0b50a4a8c_781x658.png 424w, https://substackcdn.com/image/fetch/$s_!Gfly!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0c0af0d-57f2-4f75-ad60-9fd0b50a4a8c_781x658.png 848w, https://substackcdn.com/image/fetch/$s_!Gfly!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0c0af0d-57f2-4f75-ad60-9fd0b50a4a8c_781x658.png 1272w, https://substackcdn.com/image/fetch/$s_!Gfly!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0c0af0d-57f2-4f75-ad60-9fd0b50a4a8c_781x658.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>The feature that sold me: &#8220;Prompt for AI Agents&#8221;</h2><p></p><p>Every CodeRabbit comment ships with a collapsible **&#129302; Prompt for AI Agents** &#8212; a machine-readable instruction formatted for a coding agent. The real one for the reaper finding:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Verify each finding against current code. Fix only still-valid issues, skip the

rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/db/botnets.go` around lines 2783 - 2796, Update the reaper&#8217;s

player_nodes UPDATE for orphaned draining nodes to clear siphon_session_id along

with resetting state and locked_at. Ensure reused nodes no longer retain stale

siphon ownership that could be acted on by delayed UnlockNodesBySiphonSession

cleanup.</code></pre></div><p>This is the workflow shift. You don&#8217;t read the comment and paste it into your agent &#8212; you hand it the prompt and it knows the file, the line range, the intent, and the validation contract. I ran my whole review-fix loop this way: collect the prompts &#8594; feed them to my coding agent &#8594; have it assess each against the current Go and fix only the still-valid ones. The review stops being a thing you *read* and becomes a thing you *execute*.</p><h2>The three review profiles</h2><p>CodeRabbit offers three, set in <code>.coderabbit.yaml</code>:</p><p><strong>Quiet:</strong> Only the most important findings, inline | Mature codebases, noisy PRs, low reviewer bandwidth |</p><p><strong>Chill:</strong> *(what I used)* | Balanced &#8212; Majors and Minors, nitpicks separated | General purpose; the sane default |</p><p><strong>Assertive:</strong>  More feedback, may feel like nitpicking | Greenfield, junior-heavy teams, strict bars |</p><p>I ran <strong>Chill</strong> and it felt right for a removal PR. Assertive on 218 files would have fatigued me; Quiet would be my pick for a hotfix where I only want &#8220;will this break prod?&#8221;</p><h2>So, is it worth it?</h2><p>Here&#8217;s the honest accounting for Go shops specifically. Go gives you a strong type system, `go vet`, `staticcheck`, and good test tooling &#8212; so a lot of what AI review catches in dynamically-typed languages, Go already catches at compile time. What&#8217;s *left* for an AI reviewer in Go is exactly what bit m<code> here: </code><strong>raw SQL embedded as string literals, interface/method consistency across a big refactor, and cross-file invariants the compiler can&#8217;</strong><code> see. </code></p><p>`botnets.go` is 3,500 lines of `pgx` queries against a state machine living in a Postgres `CHECK` constraint &#8212; no Go tool in my toolchain reasons about the SQL inside those backticks. CodeRabbit does.</p><p>The mental model that worked for me: <strong>CodeRabbit is a very fast, very thorough junior reviewer who never gets tired but never says &#8220;I don&#8217;t know, what do you think?&#8221;</strong> It always has an opinion, and some will be wrong in the particular way a competent-but-context-light reviewer&#8217;s are. Your job shifts from *finding* issues to *adjudicating* them &#8212; strictly faster work.</p><p>On a 74,000-line removal PR, it caught two real things I&#8217;d missed and cost me maybe ten minutes adjudicating the few misses. For a Go team too small for thorough human review &#8212; it&#8217;s worth a serious look.</p><p><strong>Review your next PR with <a href="https://coderabbit.link/alex-cli">CodeRabbit Review</a> Today</strong></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://packagemain.tech/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">packagemain.tech is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[My thoughts on the future of Go in the AI era]]></title><description><![CDATA[One slightly controversial opinion I have is that AI might actually make boring languages more valuable, not less.]]></description><link>https://packagemain.tech/p/my-thoughts-on-the-future-of-go-in-ai-era</link><guid isPermaLink="false">https://packagemain.tech/p/my-thoughts-on-the-future-of-go-in-ai-era</guid><dc:creator><![CDATA[Alex Pliutau]]></dc:creator><pubDate>Wed, 15 Jul 2026 19:20:17 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/dfda9429-629f-43bf-bb89-9296c94ce82d_2912x2096.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>To be fully honest, nowadays I don&#8217;t write as much Go code as I wanted.</p><p>One reason is that we don&#8217;t use Go at my current company. We mostly use TypeScript, about which I still have mixed feelings, but to be fair, it works pretty well for us nevertheless. So I won&#8217;t blame TypeScript much here.</p><p>I still maintain a few Go projects, so I write at least some amount of Go from time to time.</p><p>But oh boy, I miss writing more of it.</p><p>Especially I miss the developer experience. Nothing even comes close for me, and interestingly, I think that becomes even more important in the AI era. And in this article I&#8217;d like to rant a bit about that.</p><div><hr></div><h3>Sponsor</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://coderabbit.link/alex-cli" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!D6hO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 424w, https://substackcdn.com/image/fetch/$s_!D6hO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 848w, https://substackcdn.com/image/fetch/$s_!D6hO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 1272w, https://substackcdn.com/image/fetch/$s_!D6hO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!D6hO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png" width="1456" height="827" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:827,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:&quot;https://coderabbit.link/alex-cli&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!D6hO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 424w, https://substackcdn.com/image/fetch/$s_!D6hO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 848w, https://substackcdn.com/image/fetch/$s_!D6hO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 1272w, https://substackcdn.com/image/fetch/$s_!D6hO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F473b0c09-bd8f-4ffe-8540-87b4dc94fb72_2048x1163.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><span>AI writes more code than ever. Reviewing it shouldn&#8217;t mean scrolling forty files in alphabetical order.</span></p><p><a href="https://coderabbit.link/alex-cli"><span>CodeRabbit Review</span></a><span> reorganizes any pull request from a flat file list into a structured, layer-by-layer walkthrough - the logical reading order of the change, not the order your platform happens to sort it. Every range gets its own plain-language summary, with sequence diagrams, state machines, and ERDs generated inline wherever a visual earns its place.</span></p><p><span>Cohorts group related files and chunks so you review one idea at a time. Layers order them so foundational changes - data shapes, contracts - come before the code that depends on them. Code Peek lets you click any variable, function, class or type to see its definition and usages without leaving the tab, while  Semantic Diff view cuts past formatting noise to show what actually changed.</span></p><p><span>Open it straight from the Review Change Stack button in the PR Walkthrough. Navigate cohorts and layers from the keyboard, comment against exact line ranges and submit native reviews, comments and approvals post back to GitHub or GitLab right where your team expects them.</span></p><p><span>In the early access, available for free to everyone.</span></p><p>From the team that pioneered AI code reviews. 2M reviews every week. 6M repos. 15K customers.The review interface built for the way modern PRs are actually written.</p><p><a href="https://coderabbit.link/alex-cli">Review your next PR with CodeRabbit Review Today</a></p><div><hr></div><p>Things are clearly changing now with coding agents, and the way we write code. I don&#8217;t mean all of us, by the way, I still have huge respect for people who manually craft software without relying heavily on AI. I think there will always be space for that level of engineering craftsmanship.</p><p>So, things are changing, for better or worse, I am still not completely sure, but you can already see some trends forming. Rust and TypeScript are becoming dominant choices in AI-generated code, while some other languages&#8230; let&#8217;s just say you hear less and less about them.</p><p>So the real question is: what programming languages will still matter in 5 years from now?</p><p>I think we will see fewer dynamic Backend languages in new projects. Ruby on Rails, PHP, maybe even Python for some Backend workloads. Not because those languages are bad, obviously. Many of them are fantastic for humans.</p><p>You see, without AI, languages competed a lot on human ergonomics. Now they also compete on machine ergonomics.</p><p>Take Ruby on Rails for example. It is incredibly human-friendly, but I would argue not necessarily machine-friendly, performance-wise and typing-wise. Humans can navigate conventions, implicit magic, and DSLs very naturally because they understand context. Models struggle more with that. Explicit systems are simply easier for them to reason about.</p><div><hr></div><p>And then there are new so-called AI-native languages.</p><ul><li><p><a href="https://zerolang.ai">Zero</a></p><ul><li><p>Honestly, reading that I am still not sure what are the strengths here</p></li></ul></li><li><p><a href="https://veralang.dev/">Vera</a></p><ul><li><p>they even route the docs differently for humans or agents. Is it different?</p></li></ul></li></ul><p>And honestly, I still don&#8217;t fully understand the pitch. Both of them feel like AI generated slop languages made with a prompt like &#8220;Hey, make me a programming language, do not make mistakes&#8221;.</p><p>Models today are trained mostly on existing languages and existing ecosystems. So how exactly are they suddenly supposed to generate perfect code in a completely new language with no mature tooling, no massive production codebase, no battle testing, and very little real-world data?</p><p>What about security, compilation? Feels like a blackbox.</p><p>So right now, ecosystems still matter a lot.</p><p>And that is where Go becomes really interesting.</p><p>Because in my opinion, and many people will disagree, Go is actually an extremely good language for LLMs. Not just for humans, but for models too. And honestly, I am really rooting for it to win this competition.</p><div><hr></div><p>So let&#8217;s review a few things that I think make Go uniquely good for the agentic era.</p><p>First, the standard library is absolutely massive and incredibly well-designed. You can build real production systems with surprisingly few dependencies. HTTP servers, JSON handling, crypto, testing, concurrency, file systems &#8212; most of it is already there.</p><p>And another important thing: the Go team takes security and stability very seriously. You rarely hear about catastrophic ecosystem-level security disasters coming from the Go standard library itself.</p><p>Then there is the compiler. Go compiles insanely fast.</p><p>That matters much more in the AI era than people realize. Humans can tolerate slower feedback loops. Agents really cannot. AI systems might compile and iterate hundreds of times while solving a task. Fast compilation directly improves that workflow.</p><p>Then you have:</p><ul><li><p>single static binary distribution</p></li><li><p>built-in formatting</p></li><li><p>powerful cross-compilation</p></li><li><p>minimal dependency hell</p></li><li><p>simple and intentionally boring syntax</p></li><li><p>Go is pragmatic. It has one mostly obvious way to do things</p><ul><li><p>And this is actually one of Go&#8217;s greatest strengths. Go intentionally removed many ways to write &#8220;creative&#8221; code. There are simply fewer ways to make a mess, which means AI-generated Go code often ends up looking surprisingly clean and maintainable.</p></li></ul></li></ul><div><hr></div><p>One of my favorite features: <a href="https://go.dev/doc/go1compat">Go&#8217;s compatibility promise</a>.</p><p>Back in Go 1, the team introduced a compatibility guarantee that basically says old programs should continue working on newer Go versions.</p><p>This sounds boring until you compare it to some modern JavaScript ecosystems where dependencies from two years ago already stop working.</p><p>I can still open Go projects I wrote almost 10 years ago and run them on a modern Go version without issues.</p><p>That level of stability is honestly extremely rare.</p><p>And I think stability might become one of the most valuable engineering properties again, especially when AI starts generating massive amounts of code. The last thing anyone wants is millions of lines of generated code depending on fragile ecosystems that break every six months.</p><div><hr></div><p>Let&#8217;s actually try it.</p><p>I found one of my old projects (10 years old): <a href="https://github.com/plutov/games">https://github.com/plutov/games</a></p><p>It just runs.</p><div><hr></div><p>I also think formatting matters much more than people admit, especially while humans still review AI-generated code.</p><p>And gofmt alone adds enormous value here.</p><p>Every Go project looks roughly the same. The style guide is basically built into the language itself. You do not spend time debating formatting rules or installing fifteen prettier plugins and linter configs.</p><p>You just run: gofmt</p><p>And when AI generates code, this consistency becomes even more valuable because stylistic entropy never really accumulates.</p><p>Then comes go vet, linting, which are also consistent between projects.</p><div><hr></div><p>Another massively underrated feature is cross-compilation.</p><p>This still feels magical to me:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">GOOS=windows GOARCH=amd64 go build
GOOS=linux GOARCH=arm64 go build</code></pre></div><p>And that&#8217;s it.</p><p>You instantly get binaries for completely different platforms without setting up complicated build pipelines or external tooling.</p><p>Again, fewer moving parts.</p><p>Fewer things that break.</p><p>That matters a lot when autonomous agents start building and deploying systems end-to-end.</p><div><hr></div><p>And again, error handling.</p><p>Some people dislike Go&#8217;s error handling because it is verbose, but others love it because it is explicit.</p><pre><code><code>err := os.ReadFile()
if err != nil {
    // ...
}
</code></code></pre><p>And honestly, for AI-generated code, explicitness is amazing.</p><p>The control flow is obvious. The failure paths are visible. There is much less hidden magic and much less ambiguity. Models generally perform better when the code is straightforward and deterministic.</p><p>You are almost forced to think about failure cases.</p><p>And that is a good thing.</p><div><hr></div><p>And then there is concurrency.</p><p>Go still has one of the nicest concurrency models ever designed.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">go fetchData()</code></pre></div><p>Even today, spawning concurrent work in Go feels absurdly lightweight compared to many other ecosystems.</p><p>Simple primitives. Simple mental model.</p><div><hr></div><p>One slightly controversial opinion I have is that AI might actually make boring languages more valuable, not less.</p><p>Because maintainability scales better than cleverness.</p><p>Generated code does not need to be genius-level engineering. It needs to be understandable, stable, refactorable, and easy to verify.</p><p>And Go is proudly boring in exactly the right ways.</p><p>I don&#8217;t know whether Go will become the dominant AI-era language.</p><p>Maybe it won&#8217;t.</p><p>But I do think it will age incredibly well.</p><p>Go still feels refreshingly simple.</p><p>And honestly, simplicity might end up being one of the most important features in the entire AI era.</p><h3>Links</h3><ul><li><p><a href="https://go.dev/doc/go1compat">Go compatibility promise</a></p></li><li><p><a href="https://coderabbit.link/alex-cli">CodeRabbit Review</a></p></li><li><p><a href="https://news.ycombinator.com/item?id=48925839">Discuss on Hacker News</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Building an Agent with the Cline SDK]]></title><description><![CDATA[In this article, we will build a small release notes generator that uses the Cline SDK to inspect recent git history and turn it into readable markdown.]]></description><link>https://packagemain.tech/p/building-an-agent-with-the-cline-sdk</link><guid isPermaLink="false">https://packagemain.tech/p/building-an-agent-with-the-cline-sdk</guid><dc:creator><![CDATA[Alex Pliutau]]></dc:creator><pubDate>Fri, 29 May 2026 09:27:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!86SY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!86SY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!86SY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png 424w, https://substackcdn.com/image/fetch/$s_!86SY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png 848w, https://substackcdn.com/image/fetch/$s_!86SY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png 1272w, https://substackcdn.com/image/fetch/$s_!86SY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!86SY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png" width="1456" height="812" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:812,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1044782,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/199209368?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!86SY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png 424w, https://substackcdn.com/image/fetch/$s_!86SY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png 848w, https://substackcdn.com/image/fetch/$s_!86SY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png 1272w, https://substackcdn.com/image/fetch/$s_!86SY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9ee246e-795a-464e-a37e-247430ae8b35_2786x1554.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><a href="https://cline.gg/alex">Cline</a> is an open-source AI coding agent focused on real software work. Most developers first encounter Cline as an assistant in the editor or terminal, but Cline is broader than a single interface.</p><p>It has both a <strong>CLI</strong> and an <strong>SDK</strong>:</p><ul><li><p>The <strong>CLI</strong> is for running agent workflows directly from the terminal</p></li><li><p>The <strong>SDK</strong> is for embedding the same agent runtime inside your own scripts, products, CI jobs, and internal tools</p></li></ul><p>That distinction matters. In the <a href="https://cline.bot/blog/introducing-cline-sdk-the-upgraded-agent-runtime">Cline SDK launch post</a>, the team explains that the original product started inside the VS Code extension, and over time the runtime became harder to separate from the IDE around it. The SDK is the answer to that problem: the agent runtime is treated as a shared service rather than an implementation detail hidden inside one app.</p><p>That is also why the SDK is more interesting than a thin API wrapper, the same runtime now powers Cline across the CLI and IDE surfaces, while staying open for other teams to embed in their own products. The low-level agent loop stays reusable, and the stateful runtime around it becomes more durable, portable, and product-agnostic.</p><p></p>
      <p>
          <a href="https://packagemain.tech/p/building-an-agent-with-the-cline-sdk">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[The Filesystem Is the API (with TigerFS)]]></title><description><![CDATA[What if the filesystem was backed by a real database?]]></description><link>https://packagemain.tech/p/the-filesystem-is-the-api-with-tigerfs</link><guid isPermaLink="false">https://packagemain.tech/p/the-filesystem-is-the-api-with-tigerfs</guid><dc:creator><![CDATA[Alex Pliutau]]></dc:creator><pubDate>Wed, 27 May 2026 18:56:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!oP1W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf87140-19ec-4466-a523-024686282bd0_2912x2096.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oP1W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf87140-19ec-4466-a523-024686282bd0_2912x2096.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oP1W!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf87140-19ec-4466-a523-024686282bd0_2912x2096.png 424w, https://substackcdn.com/image/fetch/$s_!oP1W!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf87140-19ec-4466-a523-024686282bd0_2912x2096.png 848w, https://substackcdn.com/image/fetch/$s_!oP1W!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf87140-19ec-4466-a523-024686282bd0_2912x2096.png 1272w, https://substackcdn.com/image/fetch/$s_!oP1W!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf87140-19ec-4466-a523-024686282bd0_2912x2096.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oP1W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf87140-19ec-4466-a523-024686282bd0_2912x2096.png" width="1456" height="1048" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ebf87140-19ec-4466-a523-024686282bd0_2912x2096.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1048,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1889765,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/198768418?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf87140-19ec-4466-a523-024686282bd0_2912x2096.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!oP1W!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf87140-19ec-4466-a523-024686282bd0_2912x2096.png 424w, https://substackcdn.com/image/fetch/$s_!oP1W!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf87140-19ec-4466-a523-024686282bd0_2912x2096.png 848w, https://substackcdn.com/image/fetch/$s_!oP1W!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf87140-19ec-4466-a523-024686282bd0_2912x2096.png 1272w, https://substackcdn.com/image/fetch/$s_!oP1W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febf87140-19ec-4466-a523-024686282bd0_2912x2096.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Most databases persist data to disk, but the underlying files are usually opaque: binary blobs, internal formats, encrypted pages. You cannot just open a file, edit a few lines, and expect the database state to change.</p><p>But using the filesystem itself as the source of truth can be surprisingly powerful.</p><p>For my personal blog, I use <a href="https://gohugo.io/?utm_source=chatgpt.com">Hugo</a>. Articles are just Markdown files in a Git repository. In a way, that already behaves like a tiny database: posts have authors, tags, metadata, version history, collaboration via Git.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">$ tree

&#9500;&#9472;&#9472; about.md
&#9500;&#9472;&#9472; an-ode-to-logging.md
&#9500;&#9472;&#9472; benchmark-grpc-protobuf-vs-http-json.md
&#9500;&#9472;&#9472; books-2025.md
&#9500;&#9472;&#9472; building-slack-bot-with-go.md
&#9500;&#9472;&#9472; centrally-collecting-events-in-go-microservices.md
&#9500;&#9472;&#9472; concurrency-data-race.md</code></pre></div><p>It is simple and ergonomic.</p><p>But once the number of files grows, searching, indexing, querying, and connecting data becomes painful. Filesystems are not databases.</p><p>So the obvious question is:</p><div class="callout-block" data-callout="true"><p>What if the filesystem <em>was</em> backed by a real database?</p></div>
      <p>
          <a href="https://packagemain.tech/p/the-filesystem-is-the-api-with-tigerfs">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Persistent multiplayer state without chaos]]></title><description><![CDATA[How I keep a live multiplayer game consistent with PostgreSQL holding the truth and Redis doing the fast work]]></description><link>https://packagemain.tech/p/persistent-multiplayer-state-without</link><guid isPermaLink="false">https://packagemain.tech/p/persistent-multiplayer-state-without</guid><dc:creator><![CDATA[Julien Singler]]></dc:creator><pubDate>Mon, 25 May 2026 15:55:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!0iSo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F832c4a73-9e45-462d-8175-6dac19d46757_532x433.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0iSo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F832c4a73-9e45-462d-8175-6dac19d46757_532x433.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0iSo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F832c4a73-9e45-462d-8175-6dac19d46757_532x433.png 424w, https://substackcdn.com/image/fetch/$s_!0iSo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F832c4a73-9e45-462d-8175-6dac19d46757_532x433.png 848w, https://substackcdn.com/image/fetch/$s_!0iSo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F832c4a73-9e45-462d-8175-6dac19d46757_532x433.png 1272w, https://substackcdn.com/image/fetch/$s_!0iSo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F832c4a73-9e45-462d-8175-6dac19d46757_532x433.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0iSo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F832c4a73-9e45-462d-8175-6dac19d46757_532x433.png" width="548" height="446.02255639097746" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/832c4a73-9e45-462d-8175-6dac19d46757_532x433.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:433,&quot;width&quot;:532,&quot;resizeWidth&quot;:548,&quot;bytes&quot;:62143,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/197402192?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F832c4a73-9e45-462d-8175-6dac19d46757_532x433.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0iSo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F832c4a73-9e45-462d-8175-6dac19d46757_532x433.png 424w, https://substackcdn.com/image/fetch/$s_!0iSo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F832c4a73-9e45-462d-8175-6dac19d46757_532x433.png 848w, https://substackcdn.com/image/fetch/$s_!0iSo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F832c4a73-9e45-462d-8175-6dac19d46757_532x433.png 1272w, https://substackcdn.com/image/fetch/$s_!0iSo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F832c4a73-9e45-462d-8175-6dac19d46757_532x433.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>In a single-player game, state is easy. There&#8217;s one player, one save file, and as long as you don&#8217;t corrupt the file, you&#8217;re fine.</p><p>In a live multiplayer game, state is the hardest problem you&#8217;ll have. Two players can act on the same target at the same time. A scheduled job (a build, a launder, a raid) needs to resolve at exactly the right moment regardless of who&#8217;s online. A player can come back after three days and expect their progress to be where they left it, plus whatever happened in the world while they were gone.</p><p>You can&#8217;t naively keep this in memory. You can&#8217;t lazily flush to disk on logout. You need a real persistence story, and you need it to handle concurrent access without melting.</p><p>Here&#8217;s the architecture I landed on.</p>
      <p>
          <a href="https://packagemain.tech/p/persistent-multiplayer-state-without">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[How I built the core loop of a browser multiplayer game]]></title><description><![CDATA[Why an async, queued action loop can still feel as immediate as a real-time game &#8212; and what it took to get there]]></description><link>https://packagemain.tech/p/how-i-built-the-core-loop-of-a-browser</link><guid isPermaLink="false">https://packagemain.tech/p/how-i-built-the-core-loop-of-a-browser</guid><dc:creator><![CDATA[Julien Singler]]></dc:creator><pubDate>Fri, 08 May 2026 16:37:26 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9adb2963-f170-465c-8142-470fcd353360_927x596.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;m kicking off a series about <a href="https://hiddenwars.io">HiddenWars</a>, a personal project I&#8217;ve been building with Golang and Svelte. It&#8217;s a persistent browser-based hacking game with mostly PvE gameplay, some PvP tension, and an evolving lore where players build botnets, upgrade their systems, hack targets, and uncover secrets while pushing back against powerful mega-corporations. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IOPb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c269d58-3f59-4d32-8bbd-ea9d69bb9aa0_1860x854.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IOPb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c269d58-3f59-4d32-8bbd-ea9d69bb9aa0_1860x854.png 424w, https://substackcdn.com/image/fetch/$s_!IOPb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c269d58-3f59-4d32-8bbd-ea9d69bb9aa0_1860x854.png 848w, https://substackcdn.com/image/fetch/$s_!IOPb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c269d58-3f59-4d32-8bbd-ea9d69bb9aa0_1860x854.png 1272w, https://substackcdn.com/image/fetch/$s_!IOPb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c269d58-3f59-4d32-8bbd-ea9d69bb9aa0_1860x854.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IOPb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c269d58-3f59-4d32-8bbd-ea9d69bb9aa0_1860x854.png" width="1456" height="669" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5c269d58-3f59-4d32-8bbd-ea9d69bb9aa0_1860x854.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:669,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:221189,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/196777927?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c269d58-3f59-4d32-8bbd-ea9d69bb9aa0_1860x854.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IOPb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c269d58-3f59-4d32-8bbd-ea9d69bb9aa0_1860x854.png 424w, https://substackcdn.com/image/fetch/$s_!IOPb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c269d58-3f59-4d32-8bbd-ea9d69bb9aa0_1860x854.png 848w, https://substackcdn.com/image/fetch/$s_!IOPb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c269d58-3f59-4d32-8bbd-ea9d69bb9aa0_1860x854.png 1272w, https://substackcdn.com/image/fetch/$s_!IOPb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c269d58-3f59-4d32-8bbd-ea9d69bb9aa0_1860x854.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Introduction</h2><p>Every game has a core loop. In a shooter it's: <code>see &#8594; aim &#8594; shoot &#8594; reload. </code>In an idle game it&#8217;s:<code> click &#8594; earn &#8594; upgrade. </code>The loop is the heartbeat of the design: the smaller it is and the better it feels, the more the rest of the game can hang off it. </p><p>For a browser-based multiplayer game, the core loop is harder than it looks. Browsers are not a great real-time runtime. Players come and go. Tabs sleep. WebSocket disconnect on Wi-Fi handovers. You cannot rely on every player being online at the same time, and you definitely cannot rely on a 60Hz tick. </p><p>So you have two choices: pretend the browser is a real-time engine and fight it, or accept that your loop is fundamentally async and design around that. </p><p>I went with the second option. Here is what I learned.</p>
      <p>
          <a href="https://packagemain.tech/p/how-i-built-the-core-loop-of-a-browser">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Agentic pre-commit hook with Opencode Go SDK]]></title><description><![CDATA[I&#8217;ve been an avid user of Opencode for a long time, even before it became widely popular.]]></description><link>https://packagemain.tech/p/agentic-pre-commit-hook-with-opencode</link><guid isPermaLink="false">https://packagemain.tech/p/agentic-pre-commit-hook-with-opencode</guid><dc:creator><![CDATA[Alex Pliutau]]></dc:creator><pubDate>Sun, 22 Mar 2026 19:05:57 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3437df14-0de2-4965-a20a-e32ea452565e_1376x768.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;ve been an avid user of <a href="https://opencode.ai">Opencode</a> for a long time, even before it became widely popular. It&#8217;s not the only coding agent in my toolkit (I also reach for <a href="https://amp.dev">Amp</a> from time to time), but Opencode holds a special place because of its LSP integration and the dead-simple ability to swap models on the fly.</p><p>It&#8217;s also remarkably extensible. You can write plugins in TypeScript, apply custom themes, build tools and web apps around it, and even extend it with Skills. The community around it is growing fast &#8212; check out <a href="https://github.com/awesome-opencode/awesome-opencode">awesome-opencode</a> or <a href="https://opencode.cafe">opencode.cafe</a> if you want to explore what people are building.</p><p>What I recently discovered, though, is that Opencode has a <strong>Go SDK</strong> (not to be confused with <em>Opencode Go</em>), and that&#8217;s what inspired this whole project. Let&#8217;s see what we can build with it.</p><h2>The Problem: Catching dumb mistakes before commit</h2><p>I&#8217;m the type of developer who absolutely hates typos in code and stray debug print statements &#8212; and also the type who produces them constantly. Regular formatters and linters don&#8217;t always catch these things, and let&#8217;s be honest: other code reviewers aren&#8217;t exactly trustworthy either. Nobody really reads the code anymore.</p><p>There are tools like CodeRabbit, GitHub Copilot, and Graphite that can review your pull requests after the fact. But what if I want to run similar checks <em>before</em> committing my code? And what if I want those checks to be configurable?</p><p>The idea: run an AI-powered code review as a <strong>pre-commit hook</strong>, using a coding agent that&#8217;s already aware of the codebase.</p><p>Since I use Opencode daily, let&#8217;s build it with that.</p>
      <p>
          <a href="https://packagemain.tech/p/agentic-pre-commit-hook-with-opencode">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Developing a 2FA Desktop Client in Go]]></title><description><![CDATA[Building a 2FA Authenticator desktop client in Go and Wails+Vue]]></description><link>https://packagemain.tech/p/2fa-desktop-client-golang-wails-vue</link><guid isPermaLink="false">https://packagemain.tech/p/2fa-desktop-client-golang-wails-vue</guid><dc:creator><![CDATA[Alex Pliutau]]></dc:creator><pubDate>Sun, 15 Mar 2026 07:41:19 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f4e64ffd-2e71-48f3-8dff-7adc21802417_2400x1350.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back! Long ago I made a <a href="https://youtu.be/Dg9rUXxNV-c">video about Wails</a>, which is a framework/library to develop desktop or cross-platform apps in Go. And recently I wanted to revisit this project, because I wanted to build a local desktop 2FA Authenticator App, similar to Google/Microsoft Authenticator and others.</p><p>I was hoping that I could try the latest version 3, but it&#8217;s not yet released. But that shouldn&#8217;t be a problem as v2 is more than enough to create a lightweight desktop app.</p><p>Short recap of how Wails works, I won&#8217;t Go into the details, you can check it yourself:</p><blockquote><p>A Wails application is a standard Go application, with a webkit frontend. It is possible to bind Go methods to the frontend, and these will appear as JavaScript methods that can be called, just as if they were local JavaScript methods.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CREJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02223e6c-6e65-4401-84ee-408e5681c56f_1024x768.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CREJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02223e6c-6e65-4401-84ee-408e5681c56f_1024x768.webp 424w, https://substackcdn.com/image/fetch/$s_!CREJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02223e6c-6e65-4401-84ee-408e5681c56f_1024x768.webp 848w, https://substackcdn.com/image/fetch/$s_!CREJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02223e6c-6e65-4401-84ee-408e5681c56f_1024x768.webp 1272w, https://substackcdn.com/image/fetch/$s_!CREJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02223e6c-6e65-4401-84ee-408e5681c56f_1024x768.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CREJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02223e6c-6e65-4401-84ee-408e5681c56f_1024x768.webp" width="1024" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/02223e6c-6e65-4401-84ee-408e5681c56f_1024x768.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:10966,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/190967482?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02223e6c-6e65-4401-84ee-408e5681c56f_1024x768.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CREJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02223e6c-6e65-4401-84ee-408e5681c56f_1024x768.webp 424w, https://substackcdn.com/image/fetch/$s_!CREJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02223e6c-6e65-4401-84ee-408e5681c56f_1024x768.webp 848w, https://substackcdn.com/image/fetch/$s_!CREJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02223e6c-6e65-4401-84ee-408e5681c56f_1024x768.webp 1272w, https://substackcdn.com/image/fetch/$s_!CREJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02223e6c-6e65-4401-84ee-408e5681c56f_1024x768.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="https://packagemain.tech/p/2fa-desktop-client-golang-wails-vue">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Golang optimizations for high‑volume services]]></title><description><![CDATA[Lessons from a Postgres &#8594; Elasticsearch pipeline]]></description><link>https://packagemain.tech/p/golang-optimizations-for-highvolume</link><guid isPermaLink="false">https://packagemain.tech/p/golang-optimizations-for-highvolume</guid><dc:creator><![CDATA[Julien Singler]]></dc:creator><pubDate>Mon, 08 Dec 2025 11:48:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!HiPm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HiPm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HiPm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!HiPm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!HiPm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!HiPm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HiPm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1583190,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/180879519?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HiPm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!HiPm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!HiPm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!HiPm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56a59d5-f31c-424d-a3fa-0eff50005175_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Intro</h2><p>Building services that sit on top of a Postgres replication slot and continuously stream data into Elasticsearch is a great way to get low&#8209;latency search without hammering your primary database with ad&#8209;hoc queries. But as soon as traffic ramps up, these services become a stress test for Go&#8217;s memory allocator, garbage collector, and JSON stack.</p><p>This post walks through optimizations applied to a real-world service that:</p><ul><li><p>Connects to a Postgres replication slot</p></li><li><p>Transforms and enriches the change events</p></li><li><p>Uses Elasticsearch&#8217;s bulk indexer to index and delete documents</p></li></ul><p>The constraints: the service cannot stop reading from the replication slot for long (or Postgres disk will grow), and it cannot buffer unbounded data in memory (or Go&#8217;s heap will). The goal is to keep latency and memory stable under sustained high volume.</p>
      <p>
          <a href="https://packagemain.tech/p/golang-optimizations-for-highvolume">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[ULID: Universally Unique Lexicographically Sortable Identifier]]></title><description><![CDATA[Using ULID identifiers in Go programs running on Postgres database.]]></description><link>https://packagemain.tech/p/ulid-identifier-golang-postgres</link><guid isPermaLink="false">https://packagemain.tech/p/ulid-identifier-golang-postgres</guid><dc:creator><![CDATA[Alex Pliutau]]></dc:creator><pubDate>Mon, 01 Dec 2025 20:30:05 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/1633f002-8754-437f-b977-f8637217ddaf_2912x2096.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The <strong>UUID</strong> format is a highly popular and amazing standard for unique identifiers. However, despite its ubiquity, it can be suboptimal for many common use-cases because of several inherent limitations:</p><ul><li><p>It isn&#8217;t the most character efficient or human-readable.</p></li><li><p><strong>UUID v1/v2</strong> is impractical in many environments, as it requires access to a unique, stable MAC address.</p></li><li><p><strong>UUID v3/v5</strong> requires a unique seed.</p></li><li><p><strong>UUID v4</strong> provides no other information than true randomness, which can lead to database fragmentation in data structures like B-trees, ultimately hurting write performance.</p></li></ul><h2>Introducing the ULID Identifier</h2><p>Few projects I worked on used the <strong>ULID (Universally Unique Lexicographically Sortable Identifier)</strong>, and I really enjoyed working with it, and would love to share this experience with you. Specifically for Go programs using Postgres database. But the same applies to other languages or databases too.</p><p>You can find the full spec here - <a href="https://github.com/ulid/spec">github.com/ulid/spec</a></p><pre><code>ulid() // 01ARZ3NDEKTSV4RRFFQ69G5FAV</code></pre>
      <p>
          <a href="https://packagemain.tech/p/ulid-identifier-golang-postgres">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Building Conway’s Game of Life in Go with raylib-go]]></title><description><![CDATA[A step-by-step tutorial on building Conway&#8217;s Game of Life in Go, using raylib-go for graphics. Learn how to draw grids, apply the rules of life, and simulate evolving patterns.]]></description><link>https://packagemain.tech/p/building-conways-game-of-life-in</link><guid isPermaLink="false">https://packagemain.tech/p/building-conways-game-of-life-in</guid><dc:creator><![CDATA[Tim Little]]></dc:creator><pubDate>Mon, 22 Sep 2025 09:11:55 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5c10c9d4-788c-4f0d-a7c8-aeb94c864962_2912x2096.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently started to play around with graphics programming and game engine creation. For that, I was using SDL2, OpenGL and C.</p><p>However, I wanted to do something in Go, so I started with <a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life">Conway&#8217;s Game of Life</a>. There are various options from C bindings to <a href="https://github.com/go-gl/gl">OpenGL</a> and <a href="https://github.com/veandco/go-sdl2">SDL2</a> to full game engines such as <a href="https://ebitengine.org/">Ebitengine</a>. I went with <a href="https://github.com/gen2brain/raylib-go">raylib-go</a> as it was a good mixture of low-level and abstracted programming.</p><p>In this tutorial, we&#8217;ll use <strong>Go</strong> and <strong>raylib-go</strong> to create Conway&#8217;s Game of Life, a simulation where simple rules create endless patterns. By the end, you&#8217;ll have gliders drifting, pulsars pulsing, and even a glider gun firing across your screen.</p><h3><strong>The Game of Life</strong></h3><p>Conway&#8217;s Game of Life is a cellular automaton created by John Conway. It is a zero-player game which evolves. Each generation is built on the previous generation using a set of simple rules.</p><p>The game requires no players as you start it off, and it starts to evolve without intervention. It is <a href="https://en.wikipedia.org/wiki/Turing_completeness">Turing Complete</a> and can simulate a wide variety of different patterns and Turing Machines.</p><p>It starts with an initial game start, then it uses the following rules to determine which cell lives or dies:</p><ol><li><p>Any live cell with <strong>fewer than 2</strong> live neighbours dies, as if by underpopulation.</p></li><li><p>Any <strong>live cell with 2 or 3</strong> live neighbours lives on to the next generation.</p></li><li><p>Any live cell with <strong>more than 3</strong> live neighbours dies, as if by overpopulation.</p></li><li><p>Any dead cell with <strong>exactly 3 live</strong> neighbours becomes a live cell, as if by reproduction.</p></li></ol><p>Each generation or frame in the context of our game will use these rules to determine the next state.</p><h3><strong>Demo</strong></h3><p>In this tutorial, we will be creating a simple cellular automation in Go using raylib-go. It will start with a state we define, and allow us to modify the state to see different evolutions.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zHEr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee10ebeb-21a2-44b0-a051-ebf84677b6b0_800x430.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zHEr!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee10ebeb-21a2-44b0-a051-ebf84677b6b0_800x430.gif 424w, https://substackcdn.com/image/fetch/$s_!zHEr!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee10ebeb-21a2-44b0-a051-ebf84677b6b0_800x430.gif 848w, https://substackcdn.com/image/fetch/$s_!zHEr!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee10ebeb-21a2-44b0-a051-ebf84677b6b0_800x430.gif 1272w, https://substackcdn.com/image/fetch/$s_!zHEr!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee10ebeb-21a2-44b0-a051-ebf84677b6b0_800x430.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zHEr!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee10ebeb-21a2-44b0-a051-ebf84677b6b0_800x430.gif" width="800" height="430" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ee10ebeb-21a2-44b0-a051-ebf84677b6b0_800x430.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:430,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!zHEr!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee10ebeb-21a2-44b0-a051-ebf84677b6b0_800x430.gif 424w, https://substackcdn.com/image/fetch/$s_!zHEr!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee10ebeb-21a2-44b0-a051-ebf84677b6b0_800x430.gif 848w, https://substackcdn.com/image/fetch/$s_!zHEr!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee10ebeb-21a2-44b0-a051-ebf84677b6b0_800x430.gif 1272w, https://substackcdn.com/image/fetch/$s_!zHEr!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee10ebeb-21a2-44b0-a051-ebf84677b6b0_800x430.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Game of life</figcaption></figure></div><p></p>
      <p>
          <a href="https://packagemain.tech/p/building-conways-game-of-life-in">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[How to implement the Outbox pattern in Go and Postgres]]></title><description><![CDATA[How and why to use the Outbox pattern to build a robust event-driven system.]]></description><link>https://packagemain.tech/p/how-to-implement-the-outbox-pattern-in-golang</link><guid isPermaLink="false">https://packagemain.tech/p/how-to-implement-the-outbox-pattern-in-golang</guid><dc:creator><![CDATA[Alex Pliutau]]></dc:creator><pubDate>Tue, 16 Sep 2025 07:57:50 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/09503238-02cd-4d09-9b82-ed02d0f3a853_2912x2096.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div id="youtube2-hJ4S-5MirvU" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;hJ4S-5MirvU&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/hJ4S-5MirvU?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>I was at a <a href="https://www.containerdays.io/">ContainerDays</a> conference recently and attended a great talk from <a href="https://www.linkedin.com/in/nkuznetsov/">Nikolay Kuznetsov</a> about the Outbox pattern and resilient system design. It seemed like a powerful solution to a common problem in distributed systems, so I decided to dive deeper, and I want to share what I've learned and summarize for our readers in this post.</p><h3>The challenge in event-driven systems</h3><p>In modern, event-driven architectures, services often communicate asynchronously using a message broker. A typical flow looks like this: a service receives a request, updates its own database, and then publishes an event to notify other services about the change. Or these two actions happen in parallel.</p><p>Here's the problem: what happens if the database commit succeeds, but the subsequent call to the message broker fails? Maybe the broker is temporarily down, or there's a network glitch. Or what if the database is not available at that time? Or what if the program somehow crashes?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0TkS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d273d30-b9a1-42d7-b9b8-ef058160a0ca_2558x1062.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0TkS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d273d30-b9a1-42d7-b9b8-ef058160a0ca_2558x1062.png 424w, https://substackcdn.com/image/fetch/$s_!0TkS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d273d30-b9a1-42d7-b9b8-ef058160a0ca_2558x1062.png 848w, https://substackcdn.com/image/fetch/$s_!0TkS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d273d30-b9a1-42d7-b9b8-ef058160a0ca_2558x1062.png 1272w, https://substackcdn.com/image/fetch/$s_!0TkS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d273d30-b9a1-42d7-b9b8-ef058160a0ca_2558x1062.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0TkS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d273d30-b9a1-42d7-b9b8-ef058160a0ca_2558x1062.png" width="1456" height="604" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2d273d30-b9a1-42d7-b9b8-ef058160a0ca_2558x1062.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:604,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:121954,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/173732554?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d273d30-b9a1-42d7-b9b8-ef058160a0ca_2558x1062.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0TkS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d273d30-b9a1-42d7-b9b8-ef058160a0ca_2558x1062.png 424w, https://substackcdn.com/image/fetch/$s_!0TkS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d273d30-b9a1-42d7-b9b8-ef058160a0ca_2558x1062.png 848w, https://substackcdn.com/image/fetch/$s_!0TkS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d273d30-b9a1-42d7-b9b8-ef058160a0ca_2558x1062.png 1272w, https://substackcdn.com/image/fetch/$s_!0TkS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d273d30-b9a1-42d7-b9b8-ef058160a0ca_2558x1062.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>You end up in an inconsistent state. Your local database has the new data, but the rest of the system never gets the notification. This is a serious issue because the database operation and the message publishing are not <strong>atomic</strong> - they don't succeed or fail as a single, indivisible unit.</p>
      <p>
          <a href="https://packagemain.tech/p/how-to-implement-the-outbox-pattern-in-golang">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[The evolution of code review practices in the world of AI]]></title><description><![CDATA[Code reviews are being redefined by AI, and I think it's taking a good direction. Let's review some of the developments in this area, and tools like CodeRabbit.]]></description><link>https://packagemain.tech/p/evolution-of-code-review-practices-code-rabbit</link><guid isPermaLink="false">https://packagemain.tech/p/evolution-of-code-review-practices-code-rabbit</guid><dc:creator><![CDATA[Alex Pliutau]]></dc:creator><pubDate>Tue, 22 Jul 2025 11:54:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/096ae803-c5d4-4565-8504-c16d298efbb4_2912x2096.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The world of software development is abuzz with AI's transformative power, largely focusing on its ability to generate code. However, coding is only a part of the intricate software development process, and while everyone is arguing about vibe coding, or hopping between endlessly new emerging VS Code forks, LLMs are making a very good adoption in few other areas, for example <strong>code reviews</strong>.</p><p>I don&#8217;t know about you, but I love code reviews.</p><h3>The power of code reviews</h3><p>There are different ways to conduct a code review (mailing lists, pull/merge requests, even the code printouts), but they all serve the same main purposes.</p><ul><li><p>&#128027; Help catch bugs early, which can save time and resources later in the development cycle.</p></li><li><p>&#9989; Help maintain high code quality and adhere to the code standards, performance considerations, etc.</p></li><li><p>&#128172; Code reviews can improve team communication. They create a platform for discussing design decisions and coding standards. This dialogue can lead to better solutions and a more cohesive team.</p></li><li><p>&#128218; Promote knowledge sharing among team members. When developers review each other's code, they learn different approaches and techniques, which can enhance their skills.</p></li></ul><p></p>
      <p>
          <a href="https://packagemain.tech/p/evolution-of-code-review-practices-code-rabbit">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Redis streams: a different take on event-driven]]></title><description><![CDATA[A different take on event driven.]]></description><link>https://packagemain.tech/p/redis-streams-event-driven</link><guid isPermaLink="false">https://packagemain.tech/p/redis-streams-event-driven</guid><dc:creator><![CDATA[Julien Singler]]></dc:creator><pubDate>Mon, 14 Jul 2025 16:00:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!6HF2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Redis Streams are a powerful data structure in Redis designed for handling real-time, high-throughput event data. They combine the simplicity of an append-only log with advanced features for event consumption and processing, making them ideal for use cases like event sourcing, sensor data collection, and notification systems.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6HF2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6HF2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6HF2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6HF2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6HF2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6HF2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg" width="860" height="573" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:573,&quot;width&quot;:860,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:137951,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/167598204?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6HF2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6HF2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6HF2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6HF2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814e951a-3665-42ef-854d-228c8d430fa7_860x573.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>What are Redis Streams ?</h2><p>A <strong>Redis stream</strong> acts as an append-only log where each new entry is assigned a unique, time-based ID. Unlike traditional logs, Redis Streams support random access in O(1) time and enable complex consumption patterns, such as consumer groups for distributed processing. This makes them suitable for scenarios where you need to record, syndicate, and process events in real time.</p><p><strong>Key characteristics:</strong></p><ul><li><p><strong>Append-only:</strong> Once data is written, it cannot be modified.</p></li><li><p><strong>Immutable entries:</strong> Each entry is a set of key-value pairs with a unique ID.</p></li><li><p><strong>Real-time syndication:</strong> Multiple consumers can process events as they arrive.</p></li><li><p><strong>Trimming and retention:</strong> Streams can be trimmed to prevent unbounded growth.</p></li></ul><p></p>
      <p>
          <a href="https://packagemain.tech/p/redis-streams-event-driven">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Developing a terminal UI in Go with Bubble Tea]]></title><description><![CDATA[Developing CLIs and TUIs in Go is fun, and there are really good packages out there to make it so. And when it comes to terminal apps, there is an amazing library called Bubble Tea to build beautiful]]></description><link>https://packagemain.tech/p/terminal-ui-bubble-tea</link><guid isPermaLink="false">https://packagemain.tech/p/terminal-ui-bubble-tea</guid><dc:creator><![CDATA[Alex Pliutau]]></dc:creator><pubDate>Mon, 07 Jul 2025 16:04:56 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/dfdb1930-d11a-4249-8d5e-bab957387a60_2912x2096.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Many developers love using command line tools for daily development tasks, at least I do. It's fun, they are usually performant. For example there are many Desktop applications to work with git, though I believe that the majority of programmers love to use git CLI (or TUIs like <a href="https://github.com/jesseduffield/lazygit">lazygit</a>) as it's faster and can be automated, and is the same on all environments: be it your dev machine or server. There are 2 main flavours of CLIs: one is imperative, command-based CLIs, such as git again, or kubectl, where you define everything in prompt using sub-commands and flags. And the second is so-called Terminal Apps or Terminal UI (commonly called TUI) which is more interactive, and is basically a whole application that runs in your terminal.</p><p>For example, there is a <a href="https://github.com/bensadeh/circumflex">circumflex</a> TUI that lets you read Hacker News from your Terminal.</p><pre><code>clx</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3sWL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe166c350-c15a-4493-9712-8febb39d6101_2670x1504.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3sWL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe166c350-c15a-4493-9712-8febb39d6101_2670x1504.png 424w, https://substackcdn.com/image/fetch/$s_!3sWL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe166c350-c15a-4493-9712-8febb39d6101_2670x1504.png 848w, https://substackcdn.com/image/fetch/$s_!3sWL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe166c350-c15a-4493-9712-8febb39d6101_2670x1504.png 1272w, https://substackcdn.com/image/fetch/$s_!3sWL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe166c350-c15a-4493-9712-8febb39d6101_2670x1504.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3sWL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe166c350-c15a-4493-9712-8febb39d6101_2670x1504.png" width="1456" height="820" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e166c350-c15a-4493-9712-8febb39d6101_2670x1504.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:820,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:407743,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/166184665?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe166c350-c15a-4493-9712-8febb39d6101_2670x1504.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3sWL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe166c350-c15a-4493-9712-8febb39d6101_2670x1504.png 424w, https://substackcdn.com/image/fetch/$s_!3sWL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe166c350-c15a-4493-9712-8febb39d6101_2670x1504.png 848w, https://substackcdn.com/image/fetch/$s_!3sWL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe166c350-c15a-4493-9712-8febb39d6101_2670x1504.png 1272w, https://substackcdn.com/image/fetch/$s_!3sWL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe166c350-c15a-4493-9712-8febb39d6101_2670x1504.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p>
      <p>
          <a href="https://packagemain.tech/p/terminal-ui-bubble-tea">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Optimizing multi-platform Docker builds (amd64 & arm64) with registry Cache]]></title><description><![CDATA[In modern CI/CD pipelines, supporting multiple architectures for Docker image builds, specifically amd64 and arm64, is crucial.]]></description><link>https://packagemain.tech/p/optimizing-multi-platform-docker</link><guid isPermaLink="false">https://packagemain.tech/p/optimizing-multi-platform-docker</guid><dc:creator><![CDATA[Julien Singler]]></dc:creator><pubDate>Tue, 17 Jun 2025 13:44:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!riLh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc078af-8db1-4e80-b00e-926927be5249_634x526.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!riLh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc078af-8db1-4e80-b00e-926927be5249_634x526.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!riLh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc078af-8db1-4e80-b00e-926927be5249_634x526.png 424w, https://substackcdn.com/image/fetch/$s_!riLh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc078af-8db1-4e80-b00e-926927be5249_634x526.png 848w, https://substackcdn.com/image/fetch/$s_!riLh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc078af-8db1-4e80-b00e-926927be5249_634x526.png 1272w, https://substackcdn.com/image/fetch/$s_!riLh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc078af-8db1-4e80-b00e-926927be5249_634x526.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!riLh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc078af-8db1-4e80-b00e-926927be5249_634x526.png" width="634" height="526" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ebc078af-8db1-4e80-b00e-926927be5249_634x526.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:526,&quot;width&quot;:634,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:13511,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/166141848?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc078af-8db1-4e80-b00e-926927be5249_634x526.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!riLh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc078af-8db1-4e80-b00e-926927be5249_634x526.png 424w, https://substackcdn.com/image/fetch/$s_!riLh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc078af-8db1-4e80-b00e-926927be5249_634x526.png 848w, https://substackcdn.com/image/fetch/$s_!riLh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc078af-8db1-4e80-b00e-926927be5249_634x526.png 1272w, https://substackcdn.com/image/fetch/$s_!riLh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Febc078af-8db1-4e80-b00e-926927be5249_634x526.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>In modern CI/CD pipelines, supporting multiple architectures for Docker image builds, specifically <strong>amd64</strong> and <strong>arm64</strong>, is crucial. This ensures compatibility for your team, whether they're on Apple silicon or Intel machines, and aligns with your production infrastructure.</p><p>If you don&#8217;t have access to Github Runners or Machines in your CI/CD with both architectures, you might be using emulation, like QEMU and it can be quite slow to build an arm64 image on an amd64 machine. </p><p>That&#8217;s why you can optimize your builds and use cache to make it faster. </p><p>This article details a robust 3-step workflow for achieving efficient multiplatform builds and covers best practices for frontend (FE) projects where static assets should be built only once for amd64.</p><p></p>
      <p>
          <a href="https://packagemain.tech/p/optimizing-multi-platform-docker">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[JSON Web Tokens in Go]]></title><description><![CDATA[JSON Web Tokens is a well known and popular open standard that defines a compact way for securely transmitting information between parties as a JSON object.]]></description><link>https://packagemain.tech/p/json-web-tokens-in-go</link><guid isPermaLink="false">https://packagemain.tech/p/json-web-tokens-in-go</guid><dc:creator><![CDATA[Alex Pliutau]]></dc:creator><pubDate>Sat, 24 May 2025 18:36:55 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b48f198f-b0bd-420e-b15a-9e6276acba69_2912x2096.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>JSON Web Tokens is a well known and popular open standard that defines a compact way for securely transmitting information between parties as a JSON object.</p><p>In this article we will dive into the standard itself to make sure we understand how JWT works. We will also implement a secure Go server that can issue JWTs and verify them. And finally we will review some best practices for securely using JWTs.</p><div class="pullquote"><p> The suggested pronunciation of JWT is the same as the English word "jot".</p></div><h2>Use Cases</h2><p>The most common use case for JWT is authorization. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token. Usually sent in a header, for example &#8220;Authorization&#8221;.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R-eS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cf9748d-766c-4851-ae57-8a0d8a19dfc4_2682x1400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R-eS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cf9748d-766c-4851-ae57-8a0d8a19dfc4_2682x1400.png 424w, https://substackcdn.com/image/fetch/$s_!R-eS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cf9748d-766c-4851-ae57-8a0d8a19dfc4_2682x1400.png 848w, https://substackcdn.com/image/fetch/$s_!R-eS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cf9748d-766c-4851-ae57-8a0d8a19dfc4_2682x1400.png 1272w, https://substackcdn.com/image/fetch/$s_!R-eS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cf9748d-766c-4851-ae57-8a0d8a19dfc4_2682x1400.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!R-eS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cf9748d-766c-4851-ae57-8a0d8a19dfc4_2682x1400.png" width="1456" height="760" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9cf9748d-766c-4851-ae57-8a0d8a19dfc4_2682x1400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:760,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:214286,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/164192611?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cf9748d-766c-4851-ae57-8a0d8a19dfc4_2682x1400.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!R-eS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cf9748d-766c-4851-ae57-8a0d8a19dfc4_2682x1400.png 424w, https://substackcdn.com/image/fetch/$s_!R-eS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cf9748d-766c-4851-ae57-8a0d8a19dfc4_2682x1400.png 848w, https://substackcdn.com/image/fetch/$s_!R-eS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cf9748d-766c-4851-ae57-8a0d8a19dfc4_2682x1400.png 1272w, https://substackcdn.com/image/fetch/$s_!R-eS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9cf9748d-766c-4851-ae57-8a0d8a19dfc4_2682x1400.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p>
      <p>
          <a href="https://packagemain.tech/p/json-web-tokens-in-go">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Real-Time database change tracking in Go: Implementing PostgreSQL CDC]]></title><description><![CDATA[Introduction]]></description><link>https://packagemain.tech/p/real-time-database-change-tracking</link><guid isPermaLink="false">https://packagemain.tech/p/real-time-database-change-tracking</guid><dc:creator><![CDATA[Julien Singler]]></dc:creator><pubDate>Wed, 07 May 2025 14:36:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!30RQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!30RQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!30RQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png 424w, https://substackcdn.com/image/fetch/$s_!30RQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png 848w, https://substackcdn.com/image/fetch/$s_!30RQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png 1272w, https://substackcdn.com/image/fetch/$s_!30RQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!30RQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png" width="728" height="310.75620975160996" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:464,&quot;width&quot;:1087,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:33758,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://packagemain.tech/i/161408784?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!30RQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png 424w, https://substackcdn.com/image/fetch/$s_!30RQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png 848w, https://substackcdn.com/image/fetch/$s_!30RQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png 1272w, https://substackcdn.com/image/fetch/$s_!30RQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41af0b9f-64b5-4d4f-abb4-f9345cbb32a4_1087x464.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Introduction</strong></h2><p>Change Data Capture (CDC) enables real-time tracking of database changes, critical for event-driven systems, analytics pipelines, and synchronizing microservices. This guide walks through implementing PostgreSQL CDC in Go using native logical replication and the <code>pgx</code> driver.</p><h2><strong>1. Understanding PostgreSQL CDC</strong></h2><p>PostgreSQL provides CDC via <strong>logical replication</strong>, which decodes changes from the write-ahead log (WAL) into consumable events (inserts/updates/deletes). Key concepts:</p><ul><li><p><strong>Replication Slots</strong>: Persistent channels for streaming changes.</p></li><li><p><strong>Publications</strong>: Define which tables to monitor.</p></li><li><p><strong>Logical Decoding Plugins</strong>: Convert WAL entries to readable formats (e.g., <code>pgoutput</code>, <code>wal2json</code>).</p></li></ul><h2><strong>2. Prerequisites</strong></h2><p><strong>a) PostgreSQL configured for replication</strong></p><pre><code># postgresql.conf  
wal_level = logical  
max_replication_slots = 5</code></pre><p>In GCP with cloudSQL, you have to enable some flags:</p><pre><code>cloudsql.logical_decoding = on </code></pre><p><strong>b) Replication user</strong></p><pre><code>CREATE ROLE repl_user WITH LOGIN REPLICATION PASSWORD 'password';  
GRANT SELECT ON ALL TABLES IN SCHEMA public TO repl_user;</code></pre><p></p>
      <p>
          <a href="https://packagemain.tech/p/real-time-database-change-tracking">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>