<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Athreya aka Maneshwar</title>
    <description>The latest articles on DEV Community by Athreya aka Maneshwar (@lovestaco).</description>
    <link>https://dev.to/lovestaco</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png</url>
      <title>DEV Community: Athreya aka Maneshwar</title>
      <link>https://dev.to/lovestaco</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lovestaco"/>
    <language>en</language>
    <item>
      <title>How We Got an LLM to Draw Charts Without Ever Touching a Pixel</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Sun, 16 Aug 2026 12:41:39 +0000</pubDate>
      <link>https://dev.to/lovestaco/how-we-got-an-llm-to-draw-charts-without-ever-touching-a-pixel-1i21</link>
      <guid>https://dev.to/lovestaco/how-we-got-an-llm-to-draw-charts-without-ever-touching-a-pixel-1i21</guid>
      <description>&lt;p&gt;Let's get something out of the way first.&lt;/p&gt;

&lt;p&gt;Having data is good. Having a database full of reviews, commits, and org activity sitting there quietly, untouched, unread, never once glanced at by a human being with a coffee and an opinion? That's not "having data." That's a very expensive data graveyard.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;LiveReview&lt;/a&gt;, we build what we call a &lt;strong&gt;Blast-Radius Aware AI Code Review for Business-Critical Systems&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Which is a fancy way of saying: we review your code, we figure out how bad it would be if a change goes wrong, and we don't shut up about it until someone fixes it.&lt;/p&gt;

&lt;p&gt;Along the way we accumulate a review data: who reviewed, how much, how fast, how often, which repos are on fire.&lt;/p&gt;

&lt;p&gt;And for a while, that pile just sat there.&lt;/p&gt;

&lt;p&gt;Engineering leaders would ask "is adoption increasing?" and get back a vibe, not an answer.&lt;/p&gt;

&lt;p&gt;So we built &lt;a href="https://livereview.hexmos.com/#/chat" rel="noopener noreferrer"&gt;Livi&lt;/a&gt;, a chat bot that answers real questions about that data with real charts, not paragraphs of hedging.&lt;/p&gt;

&lt;p&gt;This post technically about how Livi draws those charts.&lt;/p&gt;

&lt;p&gt;Specifically: why we never let the LLM touch a pixel, how the same chart definition ends up as both a live interactive graph in your browser and a flat PNG in a Slack thread, and why teaching a language model to pick the &lt;em&gt;right&lt;/em&gt; chart shape is a surprisingly deep rabbit hole.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core decision: don't ask the LLM to draw, ask it to describe
&lt;/h2&gt;

&lt;p&gt;The tempting, wrong idea is: "let's have the LLM generate an image." Please don't.&lt;/p&gt;

&lt;p&gt;Image-generating models are a different beast entirely, and even if you got one to draw a bar chart, you'd have no way to verify the numbers on it are real.&lt;/p&gt;

&lt;p&gt;You'd be trusting a model that hallucinates plausible-sounding review counts to also render them faithfully into pixels.&lt;/p&gt;

&lt;p&gt;That's not a chart, that's chart-shaped fan fiction.&lt;/p&gt;

&lt;p&gt;The actually good idea, and the one every serious LLM-charting integration eventually converges on, is: the LLM writes &lt;strong&gt;&lt;a href="https://vega.github.io/vega-lite/" rel="noopener noreferrer"&gt;Vega-Lite&lt;/a&gt;&lt;/strong&gt;, a JSON grammar for describing charts declaratively.&lt;/p&gt;

&lt;p&gt;You don't say "draw a blue bar going up." You say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mark"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"encoding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"month"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"temporal"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"review_count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"quantitative"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;That's the entire chart.&lt;/p&gt;

&lt;p&gt;No pixels, no drawing, just a description of &lt;em&gt;what the data means and how it should be mapped to a picture&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vega.github.io/vega-lite/" rel="noopener noreferrer"&gt;Vega-Lite&lt;/a&gt; does the actual drawing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftru90jvalzk8lwt06ug7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftru90jvalzk8lwt06ug7.png" alt=" " width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The LLM's job shrinks down to something it's genuinely good at: filling in a well-defined schema.&lt;/p&gt;

&lt;p&gt;Models are much better at "pick &lt;code&gt;mark: bar&lt;/code&gt; or &lt;code&gt;mark: line&lt;/code&gt;" than "hallucinate 600 pixels of a correct y-axis."&lt;/p&gt;

&lt;p&gt;And critically: the LLM never sees the actual numbers before they're rendered.&lt;/p&gt;

&lt;p&gt;It writes the SQL, we run it, and the real result set gets stitched into &lt;code&gt;data.values&lt;/code&gt; by our own Go code.&lt;/p&gt;

&lt;p&gt;The model can be as creative as it wants about &lt;em&gt;presentation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It gets zero creative license over &lt;em&gt;the numbers&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What actually happens between "how many reviews last month" and a chart on your screen
&lt;/h2&gt;

&lt;p&gt;Here's the pipeline, roughly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F40huqbyjc5i2oy64m888.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F40huqbyjc5i2oy64m888.png" alt=" " width="800" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few things worth dwelling on here, because each one exists because something went wrong first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why two SQL-writing steps instead of one?&lt;/strong&gt; Because the model needs to know how many rows the answer will have &lt;em&gt;before&lt;/em&gt; it can decide whether a chart makes sense or whether it should hand you a CSV instead.&lt;/p&gt;

&lt;p&gt;Nobody wants a bar chart with 4,000 bars.&lt;/p&gt;

&lt;p&gt;So step one is basically "how big is this going to be," and step two is "okay, now actually get me the data and tell me how to draw it."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is there a SQL guard at all?&lt;/strong&gt; Because an LLM writing raw SQL against a multi-tenant database is one confidently-worded prompt away from &lt;code&gt;org_id = 1 OR 1 = 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We run every generated query through a guard that rejects anything that isn't read-only, checks every table against a denylist, and specifically looks for the shape of a tenant-isolation bypass (constant-vs-constant comparisons, bare &lt;code&gt;OR TRUE&lt;/code&gt;, all the classics).&lt;/p&gt;

&lt;p&gt;It's not glamorous work, but it's the difference between "cool AI feature" and "why is Org A looking at Org B's review data" showing up in an incident channel.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;(meme placeholder: "Well Yes, But Actually No" / bike fall guy. Top text: "the query has an &lt;code&gt;org_id&lt;/code&gt; filter." Bottom text (mid-fall): "&lt;code&gt;WHERE org_id = 1 OR 1=1&lt;/code&gt;")&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why does the LLM only ever see a &lt;em&gt;narrowed&lt;/em&gt; slice of the schema, not the whole database?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because our actual schema has north of fifty tables, and dumping all of them into every prompt is both expensive and a great way to get the model confused about which &lt;code&gt;created_at&lt;/code&gt; belongs to which table. More on this in a second, it deserves its own section.&lt;/p&gt;
&lt;h2&gt;
  
  
  Teaching the model which tables even exist: dbctx
&lt;/h2&gt;

&lt;p&gt;Here's a problem that doesn't show up until your schema stops being a toy demo.&lt;/p&gt;

&lt;p&gt;We're at 58 tables and counting: reviews, pull requests, AI comments, review feedback, billing, licensing, job queues, the works.&lt;/p&gt;

&lt;p&gt;If we pasted the full schema into every single prompt, we'd be burning thousands of tokens per question just describing &lt;code&gt;license_seat_assignments&lt;/code&gt; to a model that was asked "how many reviews happened last month" and does not, will never, care.&lt;/p&gt;

&lt;p&gt;So instead of "here is the entire database, good luck," we use &lt;a href="https://github.com/shrsv/dbctx" rel="noopener noreferrer"&gt;dbctx&lt;/a&gt;, a Go library built specifically for this problem: given a natural-language question and a live Postgres connection, hand back only the tables that actually matter, formatted as compact, LLM-friendly text instead of a raw &lt;code&gt;information_schema&lt;/code&gt; dump.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/shrsv" rel="noopener noreferrer"&gt;
        shrsv
      &lt;/a&gt; / &lt;a href="https://github.com/shrsv/dbctx" rel="noopener noreferrer"&gt;
        dbctx
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Compile a PostgreSQL database into compact, queryable context.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;dbctx&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://pkg.go.dev/github.com/shrsv/dbctx" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5a0575e4e3043f7c4ba4617d1ce8fb45c94aeb6a82f66e9ad304c6736fcb42b1/68747470733a2f2f706b672e676f2e6465762f62616467652f6769746875622e636f6d2f73687273762f64626374782e737667" alt="Go Reference"&gt;&lt;/a&gt;
&lt;a href="https://github.com/shrsv/dbctx/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667" alt="License: MIT"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Compile a PostgreSQL database into compact, queryable context.&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;dbctx&lt;/strong&gt; is a Go library and CLI tool that compiles a PostgreSQL database into a portable, queryable context index (&lt;code&gt;.dtx&lt;/code&gt; file). It extracts schema, relationships, field semantics, representative values, JSONB structure, and builds a full-text search index — all from deterministic introspection, statistics, and heuristics, with no generative LLM and no external services required for the core index. It also supports an optional local semantic embedding signal and an optional, user-controlled terminology dictionary — both additive, both off-by-default-cost, described below.&lt;/p&gt;
&lt;p&gt;Use it to give text-to-SQL systems, AI agents, and database-aware applications a compact, relevant slice of your database schema at query time, instead of dumping the entire &lt;code&gt;information_schema&lt;/code&gt; into every prompt.&lt;/p&gt;
&lt;p&gt;Longer writeup with real numbers and diagrams: &lt;strong&gt;&lt;a href="https://journal.hexmos.com/introducing-dbctx/" rel="nofollow noopener noreferrer"&gt;Introducing dbctx&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Natural-language query&lt;/strong&gt; — find relevant tables, columns, and relationships from a text question&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic retrieval (optional, on&lt;/strong&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/shrsv/dbctx" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;It does this with a genuinely layered retrieval pipeline, not just a keyword grep.&lt;/p&gt;

&lt;p&gt;Lexical and fuzzy matching against table and column names, full-text search, matching against actual sampled values in the data, an optional semantic embedding pass, and a curated terminology layer we feed it ourselves (so "LOC" resolves to &lt;code&gt;billable_loc&lt;/code&gt;, and "MR" and "PR" both resolve to the same underlying pull-request concept, because our users say both depending on which Git host they came from).&lt;/p&gt;

&lt;p&gt;Whatever scores above zero gets pulled in, plus anything reachable through a foreign key from something that scored, because a join target with zero lexical overlap with the question is still often exactly what the query needs.&lt;/p&gt;

&lt;p&gt;The payoff is not subtle.&lt;/p&gt;

&lt;p&gt;On our real schema, a typical question narrows 58 tables down to somewhere around 25 to 30, which is roughly half the context gone before the model has written a single character of SQL.&lt;/p&gt;

&lt;p&gt;That's not a rounding-error optimization, that's the difference between a prompt the model can actually reason clearly about and one where the important tables are buried in a wall of billing and license-seat noise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4cspe51rnuqmkcnl1jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4cspe51rnuqmkcnl1jq.png" alt=" " width="800" height="90"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The part where pixels finally show up: vl-convert
&lt;/h2&gt;

&lt;p&gt;So now we have a Vega-Lite spec. Great.&lt;/p&gt;

&lt;p&gt;If the user is on the web dashboard, we're basically done, more on that in a second.&lt;/p&gt;

&lt;p&gt;But what about Slack? What about Discord? Those platforms don't run a JavaScript charting library inside a chat message.&lt;/p&gt;

&lt;p&gt;A Slack message is not a browser tab.&lt;/p&gt;

&lt;p&gt;You cannot politely ask Slack to interpret a Vega-Lite spec and render SVG for you.&lt;/p&gt;

&lt;p&gt;So for anywhere that isn't our own frontend, we need an actual image file.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://github.com/vega/vl-convert" rel="noopener noreferrer"&gt;vl-convert&lt;/a&gt; comes in: a Rust binary (with Python and Node bindings, but we shell out to the CLI) that takes a Vega-Lite spec and rasterizes it straight to PNG, no headless browser required.&lt;/p&gt;

&lt;p&gt;That last part matters more than it sounds.&lt;/p&gt;

&lt;p&gt;The old-and-busted way to render a chart server-side is to spin up a headless Chrome instance, load a page with a charting library, screenshot it, and pray your Docker image doesn't balloon to two gigabytes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vl-convert&lt;/code&gt; skips all of that.&lt;/p&gt;

&lt;p&gt;It's a single binary, it takes JSON in, it gives PNG bytes out, and it's fast enough that nobody notices it happening.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;(meme placeholder: Kombucha Girl, disgusted-then-intrigued two-panel. Disgusted panel: "spin up headless Chrome to screenshot a chart." Intrigued panel: "one Rust binary, JSON in, PNG out.")&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Same spec, two very different destinies
&lt;/h2&gt;

&lt;p&gt;Here's the part I actually think is neat.&lt;/p&gt;

&lt;p&gt;We generate &lt;strong&gt;one&lt;/strong&gt; Vega-Lite spec per chart.&lt;/p&gt;

&lt;p&gt;What happens to it next depends entirely on where it's going.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;(meme placeholder: Trade Offer / Minecraft villager trading. Give: "one Vega-Lite spec." Take: "a live interactive chart in the browser, or a flat PNG in a Slack thread, depending on where it lands.")&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8ntk6tz7uzoxzw79zlff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8ntk6tz7uzoxzw79zlff.png" alt=" " width="590" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the web, the frontend just hands the raw spec to &lt;a href="https://github.com/vega/react-vega" rel="noopener noreferrer"&gt;react-vega&lt;/a&gt; and lets the browser do the work.&lt;/p&gt;

&lt;p&gt;You get hover tooltips, you get resizing, you get an actually interactive chart, and our backend does zero image rendering for that path.&lt;/p&gt;

&lt;p&gt;It just ships JSON.&lt;/p&gt;

&lt;p&gt;For Slack and Discord, the exact same spec gets routed through &lt;code&gt;vl-convert&lt;/code&gt; instead, turned into a flat PNG, and attached as a file to the message.&lt;/p&gt;

&lt;p&gt;The bot doesn't know or care that it's the "same" chart taking a different road.&lt;/p&gt;

&lt;p&gt;From the pipeline's point of view, a Vega-Lite spec is just a Vega-Lite spec.&lt;/p&gt;

&lt;p&gt;Where it ends up decides whether it becomes living, breathing SVG or a JPEG-adjacent screenshot sitting quietly in a chat thread forever.&lt;/p&gt;

&lt;p&gt;This split is also why we can add new chart destinations cheaply.&lt;/p&gt;

&lt;p&gt;Want an emailed weekly digest with embedded charts? Same spec, same &lt;code&gt;vl-convert&lt;/code&gt; path, new delivery mechanism.&lt;/p&gt;

&lt;p&gt;The hard problem (getting a &lt;em&gt;correct&lt;/em&gt;, &lt;em&gt;sensible&lt;/em&gt; chart spec out of an LLM) is solved exactly once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this leaves us
&lt;/h2&gt;

&lt;p&gt;The whole point of &lt;a href="https://livereview.hexmos.com/#/chat" rel="noopener noreferrer"&gt;Livi&lt;/a&gt; was never "add a chatbot," it was "close the loop between the data we're already collecting and the person who actually needs to act on it."&lt;/p&gt;

&lt;p&gt;A CTO asking "are engineers actually using this thing" should get an answer that looks like a calendar heatmap of usage rhythm, not a spreadsheet and a shrug.&lt;/p&gt;

&lt;p&gt;The recipe, if you're building something similar, is genuinely not complicated:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Never let the model touch pixels. Let it write a declarative spec.&lt;/li&gt;
&lt;li&gt;Never let the model see real numbers before you've run its query yourself.&lt;/li&gt;
&lt;li&gt;Guard the query like you mean it, not like a vibes-based regex.&lt;/li&gt;
&lt;li&gt;Pick one rendering pipeline (&lt;code&gt;vl-convert&lt;/code&gt;, in our case) and let &lt;em&gt;destination&lt;/em&gt; decide static-vs-interactive, not the model.&lt;/li&gt;
&lt;li&gt;Budget real prompt space for chart &lt;em&gt;taste&lt;/em&gt;. This is the part that actually takes iteration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;LiveReview reviews your code, tells you how bad a change could go, and won't shut up until it's fixed — and Livi turns all that accumulated review data into charts a human can actually act on.&lt;/p&gt;

&lt;p&gt;If you liked the post, drop a ⭐ on the &lt;a href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;repo&lt;/a&gt; and try &lt;a href="https://hexmos.com/livereview/" rel="noopener noreferrer"&gt;LiveReview&lt;/a&gt; now.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Guy Who Invented the Internet's Front Door and Refused to Charge Rent</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:31:35 +0000</pubDate>
      <link>https://dev.to/lovestaco/the-guy-who-invented-the-internets-front-door-and-refused-to-charge-rent-4igd</link>
      <guid>https://dev.to/lovestaco/the-guy-who-invented-the-internets-front-door-and-refused-to-charge-rent-4igd</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc?utm_source=ratatop" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Okay so here's a fun one for you.&lt;/p&gt;

&lt;p&gt;Imagine you invent the thing that eventually becomes the substrate for Google, Facebook, Amazon, your bank, your ex's Instagram, and every cursed cookie consent banner known to man.&lt;/p&gt;

&lt;p&gt;Now imagine you had the legal right to charge a licensing fee for it. &lt;/p&gt;

&lt;p&gt;Like, a &lt;em&gt;reasonable&lt;/em&gt; one. A cent per page load, say.&lt;/p&gt;

&lt;p&gt;You would never have to work again. &lt;/p&gt;

&lt;p&gt;Your great-great-grandchildren would never have to work again. &lt;/p&gt;

&lt;p&gt;You'd be sipping something expensive on a boat named after a HTTP status code.&lt;/p&gt;

&lt;p&gt;Tim Berners-Lee looked at that exact opportunity in 1993 and said, essentially, "nah, you guys keep it."&lt;/p&gt;

&lt;p&gt;This has been rattling around in my head for days, so let's talk about it properly, with all the nerdy details.&lt;/p&gt;

&lt;h2&gt;
  
  
  The web almost lost to a gopher (literally)
&lt;/h2&gt;

&lt;p&gt;Berners-Lee built the World Wide Web in 1989 at CERN, laid out in a proposal called &lt;a href="https://cds.cern.ch/record/369245" rel="noopener noreferrer"&gt;Information Management: A Proposal&lt;/a&gt;, mostly so physicists could stop emailing each other giant papers and just... link to things. Wild concept, I know.&lt;/p&gt;

&lt;p&gt;But here's the part people forget: the Web wasn't the obvious winner in the early 90s. &lt;/p&gt;

&lt;p&gt;It had a genuine rival called &lt;a href="https://hackaday.com/2021/09/28/gopher-the-competing-standard-to-www-in-the-90s-is-still-worth-checking-out/" rel="noopener noreferrer"&gt;Gopher&lt;/a&gt;, built at the University of Minnesota, and for a while Gopher was winning. &lt;/p&gt;

&lt;p&gt;It was simpler, it was faster on the slow modems of the era, and it had a head start in adoption among universities and libraries.&lt;/p&gt;

&lt;p&gt;Then in February 1993, the University of Minnesota did something that, in hindsight, ranks among the great unforced errors in computing history: they announced they'd start charging licensing fees for commercial use of Gopher server software. &lt;/p&gt;

&lt;p&gt;Reasonable-sounding at the time (they needed to fund development), catastrophic in practice. &lt;/p&gt;

&lt;p&gt;The developer community, which had spent years contributing code for free on the assumption this was an open commons, felt betrayed. &lt;/p&gt;

&lt;p&gt;People started actively hunting for an exit.&lt;/p&gt;

&lt;p&gt;That exit was sitting right there, and it was free.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fecarx7yd1a0nxsskoenl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fecarx7yd1a0nxsskoenl.png" alt=" " width="319" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Worth noting: Berners-Lee and his CERN collaborator Robert Cailliau had been lobbying CERN's legal department for this &lt;em&gt;before&lt;/em&gt; the Gopher fiasco. &lt;/p&gt;

&lt;p&gt;They saw the fee-wall problem coming and wanted to preempt it. &lt;/p&gt;

&lt;p&gt;Gopher's mistake didn't create the opportunity, it just handed the Web a very well timed advertisement for the opposite approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  April 30th, 1993: the day CERN gave away a gold mine
&lt;/h2&gt;

&lt;p&gt;CERN's directors signed a document putting the Web's source code (the basic client, the basic server, the common library) into the public domain. &lt;/p&gt;

&lt;p&gt;Not "free with attribution." &lt;/p&gt;

&lt;p&gt;Not "free for non-commercial use." &lt;/p&gt;

&lt;p&gt;Public domain. &lt;/p&gt;

&lt;p&gt;Anyone, anywhere, for any purpose, forever, no royalties, no lawyers, no asking permission.&lt;/p&gt;

&lt;p&gt;You can still read the actual &lt;a href="https://cds.cern.ch/record/1164399" rel="noopener noreferrer"&gt;CERN document server record&lt;/a&gt; for this release, and CERN itself has a nice writeup on the &lt;a href="https://www.w3.org/blog/2023/30th-anniversary-of-licensing-the-web-for-general-use-and-at-no-cost/" rel="noopener noreferrer"&gt;30th anniversary of the decision&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;It's a short, dry, bureaucratic memo. &lt;/p&gt;

&lt;p&gt;Absolutely nothing in the formatting suggests "this sentence just prevented the internet from having toll booths on every hyperlink."&lt;/p&gt;

&lt;p&gt;Here's the part that gets me every time I think about it: this wasn't the default outcome. &lt;/p&gt;

&lt;p&gt;Patents on protocols were completely normal. &lt;/p&gt;

&lt;p&gt;Unisys famously enforced patent licensing on the LZW compression used in GIF files for years, and that was just an image format, not the entire architecture of networked information. &lt;/p&gt;

&lt;p&gt;Berners-Lee had a much stronger claim to a much bigger prize, and he actively worked to make sure nobody, including himself, could ever collect on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj5dwwrekwhtm73car7r9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj5dwwrekwhtm73car7r9.png" alt=" " width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So how much did he actually give up?
&lt;/h2&gt;

&lt;p&gt;Nobody can put an exact number on "what if every website paid a cent per page view since 1993," but you can get a feel for the scale by looking at who &lt;em&gt;did&lt;/em&gt; get rich building on top of what Berners-Lee gave away for free. &lt;/p&gt;

&lt;p&gt;Mark Zuckerberg built a social graph on top of the Web and became one of the richest people alive. &lt;/p&gt;

&lt;p&gt;Jeff Bezos built a bookstore on top of it and did the same. &lt;/p&gt;

&lt;p&gt;Neither of them invented the medium they got rich on. &lt;/p&gt;

&lt;p&gt;The guy who did invent it drew a government researcher's salary and later ran the &lt;a href="https://www.w3.org/" rel="noopener noreferrer"&gt;World Wide Web Consortium&lt;/a&gt; on a nonprofit budget.&lt;/p&gt;

&lt;p&gt;He's not poor, to be clear. He's had a comfortable, respected, knighted career. &lt;/p&gt;

&lt;p&gt;But "comfortable" and "owns a controlling stake in the global information economy" are very different tax brackets, and he had a real, legally available path to the second one and turned it down on principle.&lt;/p&gt;

&lt;p&gt;If you want a mental model for this, picture the inventor of the highway system also personally owning the patent on wheels, and then just... not charging tolls, ever, to anyone, on purpose.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1rab0noaxrt33h3f098.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1rab0noaxrt33h3f098.png" alt=" " width="360" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Public domain vs. "open source": the detail everyone glosses over
&lt;/h2&gt;

&lt;p&gt;Quick technical aside because this matters and it's easy to gloss over.&lt;/p&gt;

&lt;p&gt;CERN's 1993 move put the Web's &lt;em&gt;code&lt;/em&gt; into the public domain, which is actually a stronger, weirder move than open sourcing it under something like the MIT or GPL license.&lt;/p&gt;

&lt;p&gt;Open source licenses still rely on copyright existing so the license terms can be enforced. &lt;/p&gt;

&lt;p&gt;Public domain says "there is no copyright claim here to enforce in the first place." &lt;/p&gt;

&lt;p&gt;It's the difference between "you may use this under these conditions" and "this belongs to no one, full stop."&lt;/p&gt;

&lt;p&gt;Later, when the W3C formalized how &lt;em&gt;web standards&lt;/em&gt; (not just that original codebase) get licensed, they landed on a Royalty-Free patent policy in 2003, meaning any patents contributed to W3C standards like HTML or CSS have to be licensed for free to implementers. &lt;/p&gt;

&lt;p&gt;That policy is a direct descendant of the 1993 decision. &lt;/p&gt;

&lt;p&gt;It's still why you can build a browser, a static site generator, or a weird little Bun script that scrapes HTML, without a patent troll showing up in your inbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  The counterfactual nobody wants to think about
&lt;/h2&gt;

&lt;p&gt;Here's the fun horrifying thought experiment: what does the internet look like if CERN had licensed the Web instead of freeing it?&lt;/p&gt;

&lt;p&gt;Best case, some other protocol wins instead (maybe a fixed-up, de-feed licensed Gopher, maybe something else entirely), and we're having this exact same conversation about a different inventor a few years later. &lt;/p&gt;

&lt;p&gt;Protocols that lock down access don't tend to become universal substrates, they become "one option among several enterprise formats," which is exactly the fate Gopher suffered once trust broke.&lt;/p&gt;

&lt;p&gt;Worst case: the Web still wins anyway because it's technically better, but every server operator owes a fee, which means every early website needs a business model before it can exist, which means the era of "anyone with a text editor can publish to the whole world for free" never happens. &lt;/p&gt;

&lt;p&gt;No GeoCities. &lt;/p&gt;

&lt;p&gt;No early blogs. &lt;/p&gt;

&lt;p&gt;No Wikipedia edited by strangers for free. &lt;/p&gt;

&lt;p&gt;The barrier to publishing stays exactly where it was before the internet: you need capital.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvu9zcujoon8yyhqulvr9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvu9zcujoon8yyhqulvr9.png" alt=" " width="360" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why almost nobody talks about this
&lt;/h2&gt;

&lt;p&gt;I think the reason this story doesn't circulate much is that it doesn't have a villain. &lt;/p&gt;

&lt;p&gt;There's no evil corporation to be mad at, no lawsuit, no dramatic betrayal.&lt;/p&gt;

&lt;p&gt;It's just a physics lab's legal department agreeing to a request from two engineers who cared more about adoption than ownership, and then that single administrative decision quietly outcompeting every other decision anyone else made for the next thirty years. &lt;/p&gt;

&lt;p&gt;Boring paperwork doesn't trend. &lt;/p&gt;

&lt;p&gt;Free stuff staying free forever doesn't make headlines the way free stuff suddenly costing money does.&lt;/p&gt;

&lt;p&gt;But it's genuinely one of the highest leverage single decisions in the entire history of technology. &lt;/p&gt;

&lt;p&gt;One memo, thirty years ago, is a big part of why you can read this post without anyone charging you, me, or dev.to a licensing fee for the privilege of using hyperlinks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe959rza2vyfr6negasz5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe959rza2vyfr6negasz5.png" alt=" " width="360" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What he's up to now
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;When I invented the World Wide Web, I envisioned technology that would empower people and enable collaboration. Somewhere along the way, we lost that human-first approach; today’s web often prioritises profits over people. Solid returns the web to its roots by giving everyone direct control over their own data &lt;br&gt;
-Sir Tim Berners-Lee&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Berners-Lee didn't cash out and vanish. &lt;/p&gt;

&lt;p&gt;He's been trying to fix the parts of the web that went sideways once the ad-funded, walled-garden era took over, most visibly through the &lt;a href="https://solidproject.org/" rel="noopener noreferrer"&gt;Solid project&lt;/a&gt;, which is an attempt to give people back control over their own data instead of it living in some platform's silo.&lt;/p&gt;

&lt;p&gt;Whether or not it succeeds, the throughline is consistent: the guy who could have owned the internet has spent three decades since trying to make sure nobody else does either.&lt;/p&gt;

&lt;p&gt;Next time someone tells you "nobody does anything for free anymore," you now have a receipt from 1993 that says otherwise, backed by literally the entire internet as evidence.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Ayo GitHub Quietly Killed the Unreviewable Mega-PR</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Mon, 10 Aug 2026 18:19:38 +0000</pubDate>
      <link>https://dev.to/lovestaco/ayo-github-quietly-killed-the-unreviewable-mega-pr-3868</link>
      <guid>https://dev.to/lovestaco/ayo-github-quietly-killed-the-unreviewable-mega-pr-3868</guid>
      <description>&lt;p&gt;If you've ever opened a PR with 47 changed files and a diff so long GitHub just gives up and shows you "Load Diff" seventeen times, this one's for you.&lt;/p&gt;

&lt;p&gt;GitHub quietly shipped what might be the biggest pull request update in years, and it's aimed squarely at that problem. &lt;/p&gt;

&lt;p&gt;Let's talk about stacked pull requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem, in one sentence
&lt;/h2&gt;

&lt;p&gt;Big PRs are where good reviews go to die. &lt;/p&gt;

&lt;p&gt;Nobody reads a 2000 line diff carefully. &lt;/p&gt;

&lt;p&gt;Some folks reach for AI code review tools like &lt;a href="https://hexmos.com/livereview/" rel="noopener noreferrer"&gt;LiveReview&lt;/a&gt; to take the edge off, and honestly that helps, but even the best reviewer (human or model) does a better job on a tight, focused diff than on a 2000 line wall. &lt;/p&gt;

&lt;p&gt;Smaller inputs, better reviews. That's true no matter who's doing the reviewing.&lt;/p&gt;

&lt;p&gt;Stacked PRs are GitHub's answer: break one massive change into a chain of small, dependent PRs, where each one only reviews the diff it actually introduces, not everything below it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0pnj4vi6djqrlud2wv78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0pnj4vi6djqrlud2wv78.png" alt="Panel 1 (Panik): opening a 2400 line PR, Panel 2 (Kalm): splitting it into a stack of 4 reviewable PRs" width="360" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What a stack actually is
&lt;/h2&gt;

&lt;p&gt;The rule is simple. You need two or more PRs in the same repo where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The bottom PR targets your trunk branch (usually &lt;code&gt;main&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Every PR after that targets the PR below it, not &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. That's the whole trick. &lt;/p&gt;

&lt;p&gt;Foundational stuff (schemas, shared types) goes at the bottom. &lt;/p&gt;

&lt;p&gt;Stuff that depends on it (API routes, UI) goes higher up the chain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxhx8k5lilnamoh5czbmp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxhx8k5lilnamoh5czbmp.png" alt=" " width="280" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's the part that surprised me: if you just do this manually with plain git, by opening PR #11 against the branch for PR #10 instead of against &lt;code&gt;main&lt;/code&gt;, GitHub now recognizes that as a stack automatically. &lt;/p&gt;

&lt;p&gt;No special tool required. &lt;/p&gt;

&lt;p&gt;It just notices the base branches form a chain and lights up a banner. &lt;/p&gt;

&lt;p&gt;Stacking isn't a git concept at all, it's purely a GitHub UI concept layered on top of branches you were already making.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's actually build one
&lt;/h2&gt;

&lt;p&gt;Enough theory. I built a real stack in one of my own repos (&lt;a href="https://github.com/lovestaco/peektea" rel="noopener noreferrer"&gt;peektea&lt;/a&gt;, a terminal file browser I maintain), using a harmless scratch file so nothing real got touched. &lt;/p&gt;

&lt;p&gt;Here's the actual terminal session, copy pasted, warts and all.&lt;/p&gt;

&lt;p&gt;First I tried to be fancy and use the CLI extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gh stack --help
unknown command "stack" for "gh"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Yeah. Turns out &lt;code&gt;gh stack&lt;/code&gt; needs a CLI extension or a newer &lt;code&gt;gh&lt;/code&gt; version than the one sitting in my &lt;code&gt;$PATH&lt;/code&gt;, and mine's from a Ubuntu apt repo that hasn't heard about this feature yet. &lt;/p&gt;

&lt;p&gt;Software, everyone. &lt;/p&gt;

&lt;p&gt;So I did it the "old school" way the docs mention, plain git branches, chained base to base, no extension needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5euod2tqoo5tfpzp8qel.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5euod2tqoo5tfpzp8qel.png" alt=" " width="685" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkmzhec1ectucdglpakzz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkmzhec1ectucdglpakzz.png" alt=" " width="649" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftpreiof2y8nputggy3m5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftpreiof2y8nputggy3m5.png" alt=" " width="629" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which, from git's point of view, gives you exactly what git always gives you: three branches sitting on top of each other like tired commuters on a train.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6y8w1a6k5xdhswvl41zm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6y8w1a6k5xdhswvl41zm.png" alt=" " width="797" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing special yet. This is just branches. &lt;/p&gt;

&lt;p&gt;The magic happens once you push them and open the PRs with the right bases:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git push -u origin stack/01-setup-database stack/02-api-endpoints stack/03-setup-frontend

$ gh pr create --base master --head stack/01-setup-database \
    --title "demo: setup database layer"
https://github.com/lovestaco/peektea/pull/10

$ gh pr create --base stack/01-setup-database --head stack/02-api-endpoints \
    --title "demo: add api endpoints"
https://github.com/lovestaco/peektea/pull/11

$ gh pr create --base stack/02-api-endpoints --head stack/03-setup-frontend \
    --title "demo: setup frontend"
https://github.com/lovestaco/peektea/pull/12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Notice the &lt;code&gt;--base&lt;/code&gt; on PR #11 is &lt;code&gt;stack/01-setup-database&lt;/code&gt;, not &lt;code&gt;master&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;That one flag is the entire secret sauce.&lt;/p&gt;
&lt;h2&gt;
  
  
  And GitHub actually notices
&lt;/h2&gt;

&lt;p&gt;This is the bit that got me. I expected to have to manually flip some setting somewhere. &lt;/p&gt;

&lt;p&gt;Instead, the second I opened the top PR of the chain, GitHub just showed a banner: "This pull request can be stacked with other pull requests," with a "Preview stack" button sitting right there.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjixjlo2bkcsm5895p6e2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjixjlo2bkcsm5895p6e2.png" alt="GitHub PR banner offering to preview and create a stack from three chained pull requests" width="798" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking it pops up a little preview that walks the whole chain, top PR down to &lt;code&gt;main&lt;/code&gt;, correctly ordered, correctly linked, with zero config from me beyond opening PRs against the right base branches.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkmo8x2afv5nbrjfgxcj5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkmo8x2afv5nbrjfgxcj5.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit "Create stack" and every PR in the list picks up a tiny progress badge, &lt;code&gt;1/3&lt;/code&gt;, &lt;code&gt;2/3&lt;/code&gt;, &lt;code&gt;3/3&lt;/code&gt;, right there in the pull request list, so you can see at a glance how deep any given PR sits in its stack without opening a single one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89va85es61eiwolyxse8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89va85es61eiwolyxse8.png" alt="Stack chain view showing the top PR stacked on the middle PR stacked on the bottom PR stacked on master" width="799" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9rkpvhlo1u0xfnx1lhvm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9rkpvhlo1u0xfnx1lhvm.png" alt="Pull requests list showing three stacked PRs each labeled with their position in the stack" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://docs.github.com/en/pull-requests/get-started/about-stacked-prs" rel="noopener noreferrer"&gt;GitHub's own docs on stacked pull requests&lt;/a&gt;, once you're happy with the whole chain there's a "merge stack" action that walks down and merges every PR in order in one go, no manual rebasing between each merge. &lt;/p&gt;

&lt;p&gt;I didn't actually pull that trigger on my demo repo (closing four browser tabs is enough chaos for one blog post), but the docs and the UI both point at it being one button for what used to be N sequential merges plus N rebases.&lt;/p&gt;

&lt;p&gt;Here's roughly what that flow looks like end to end, CLI or manual, doesn't matter which:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe0u7x2wpwf9krqzrmdzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe0u7x2wpwf9krqzrmdzi.png" alt=" " width="800" height="719"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Wait, didn't I already have this?
&lt;/h2&gt;

&lt;p&gt;Kind of, and this is worth being honest about. You could always open a PR against another PR's branch. &lt;/p&gt;

&lt;p&gt;That's not new, that's just git plus GitHub's base branch dropdown, and people have been daisy chaining branches like this for a decade. &lt;/p&gt;

&lt;p&gt;What's new is that GitHub now understands the relationship, tracks it as a first class object (mine got assigned its own stack ID the moment I hit "Create stack"), shows progress across the whole list view, and gives you one merge action instead of a manual merge-then-rebase-then-merge dance for every layer.&lt;/p&gt;

&lt;p&gt;So no, git itself didn't get a new stacked PR primitive. &lt;/p&gt;

&lt;p&gt;GitHub's UI did. &lt;/p&gt;

&lt;p&gt;If you clone the repo and look at the branches, there is genuinely nothing different about them from any other feature branch. &lt;/p&gt;

&lt;p&gt;The "stack" only exists as metadata GitHub keeps on its side.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu9u3bwzttgurpyfxoi13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu9u3bwzttgurpyfxoi13.png" alt="Left arm: me, finally deleting my  raw `fix-2-for-real-this-time` endraw  branch naming scheme, Right arm: GitHub's native stack tracking, Handshake label: one dependency chain, zero manual rebasing" width="360" height="258"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I actually care about this (hint: it's the bots)
&lt;/h2&gt;

&lt;p&gt;Here's the part that got me more excited than a database migration screen usually gets me. &lt;/p&gt;

&lt;p&gt;Think about what claude does when you point it at a big task and let it run for a few hours unattended. &lt;/p&gt;

&lt;p&gt;Historically you get one of two bad outcomes: either it hands you back one enormous PR that's basically unreviewable, or it opens a dozen totally disconnected PRs that don't tell you which one depends on which.&lt;/p&gt;

&lt;p&gt;Stacked PRs give agents a third option: build the feature the way a careful human would, in reviewable layers, and let the dependency chain itself communicate the order. &lt;/p&gt;

&lt;p&gt;Schema first, then the API that needs the schema, then the UI that needs the API. You review it the same way the agent built it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhch69wj5kfsxsavwdxwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhch69wj5kfsxsavwdxwe.png" alt=" " width="800" height="115"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No more choosing between "one unreviewable 3000 line PR" and "twelve PRs with no visible relationship to each other." &lt;/p&gt;

&lt;p&gt;The stack is the changelog of how the agent actually thought through the problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzr5gib4nl09f6jpb75wz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzr5gib4nl09f6jpb75wz.png" alt=" " width="360" height="312"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Should you actually use this
&lt;/h2&gt;

&lt;p&gt;If your team already splits work into small PRs by hand and just eats the rebase pain, yes, obviously, this removes the pain and keeps the habit.&lt;/p&gt;

&lt;p&gt;If your team is currently a "one PR per feature, no matter how big" shop, stacked PRs are a nice on ramp, because you get to keep working the way you already do (branch off the previous branch) while GitHub handles the bookkeeping you used to do in your head.&lt;/p&gt;

&lt;p&gt;It's still labeled as a newer feature in GitHub's docs, so expect some rough edges (my &lt;code&gt;gh&lt;/code&gt; CLI didn't even know the command existed yet), but the web UI side of it worked exactly as advertised the first time I tried it, no config, no opt in flag, just open PRs with the right base branches and watch GitHub connect the dots.&lt;/p&gt;

&lt;p&gt;Go forth and stack responsibly. And maybe update your &lt;code&gt;gh&lt;/code&gt; CLI before you try the extension route, unlike me.&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
      <category>github</category>
    </item>
    <item>
      <title>Teaching Your AI Web Design Some Actual Taste</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Sat, 08 Aug 2026 17:30:42 +0000</pubDate>
      <link>https://dev.to/lovestaco/teaching-your-ai-web-design-some-actual-taste-4p13</link>
      <guid>https://dev.to/lovestaco/teaching-your-ai-web-design-some-actual-taste-4p13</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc?utm_source=ratatop" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let me guess. &lt;/p&gt;

&lt;p&gt;You opened Claude Code, typed "build me a modern landing page," hit enter, and got back something that technically works but somehow looks like every other site you have ever scrolled. &lt;/p&gt;

&lt;p&gt;Purple to blue gradients. &lt;/p&gt;

&lt;p&gt;Inter for everything. &lt;/p&gt;

&lt;p&gt;Little rounded icon tiles floating above every heading. &lt;/p&gt;

&lt;p&gt;A hero section that could belong to literally any SaaS on earth.&lt;/p&gt;

&lt;p&gt;Congratulations, you have been served a fresh bowl of AI slop.&lt;/p&gt;

&lt;p&gt;I have been fighting this exact fight for a while now, and I want to walk you through how I actually beat it. &lt;/p&gt;

&lt;p&gt;Not with one magic prompt, and definitely not with a 10,000 line &lt;code&gt;DESIGN.md&lt;/code&gt; you copied from a thread. &lt;/p&gt;

&lt;p&gt;Just three moves, a couple of tools, and a way of working that stops leaving your output to the gods of the one shot.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, why does slop even happen?
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable truth, and it is worth sitting with for a second.&lt;/p&gt;

&lt;p&gt;The problem with slop is not technical. &lt;/p&gt;

&lt;p&gt;The models are not bad at writing CSS.&lt;/p&gt;

&lt;p&gt;They are frankly great at it. &lt;/p&gt;

&lt;p&gt;The problem is that the output is &lt;em&gt;generic&lt;/em&gt;, and generic is a moving target.&lt;/p&gt;

&lt;p&gt;If the models got ten times better tomorrow, they would just raise the bar on what counts as basic. &lt;/p&gt;

&lt;p&gt;The floor moves up, the slop moves with it.&lt;/p&gt;

&lt;p&gt;Why? Because a model trained on the public internet learns the statistical center of the public internet, and the center of the design internet in 2026 is Tailwind templates, Vercel starters, and other AI generated landing pages. &lt;/p&gt;

&lt;p&gt;Ask for "modern" and you get the mathematical average of every landing page ever shipped. That average is the slop.&lt;/p&gt;

&lt;p&gt;So the fix is not a better model. &lt;/p&gt;

&lt;p&gt;The fix is &lt;em&gt;you&lt;/em&gt;. Specifically, your taste. And that is where a lot of us cope instead of cook.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9kijgt5tpamsym4bbfig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9kijgt5tpamsym4bbfig.png" alt=" " width="360" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yeah. That is the cope. &lt;/p&gt;

&lt;p&gt;The rest of this post is about doing the opposite.&lt;/p&gt;

&lt;p&gt;Here is the whole game on one screen before we dig in:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu84o7s4cp50nrge0nekx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu84o7s4cp50nrge0nekx.png" alt=" " width="319" height="151"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Cultivate some taste (you cannot inject what you do not have)
&lt;/h2&gt;

&lt;p&gt;You have heard "AI has no taste" a hundred times. Fine. &lt;/p&gt;

&lt;p&gt;But the sneaky corollary nobody says out loud is that &lt;em&gt;you&lt;/em&gt; need taste to hand over, and most of us have not deliberately built any. We just vibe.&lt;/p&gt;

&lt;p&gt;So step one is boring and unglamorous and completely non negotiable: go collect stuff you like.&lt;/p&gt;

&lt;p&gt;Become a magpie. Open &lt;a href="https://dribbble.com" rel="noopener noreferrer"&gt;Dribbble&lt;/a&gt;, search "web design," sort by popular, and start screenshotting the hero pages that make you feel something. &lt;/p&gt;

&lt;p&gt;Do the same on &lt;a href="https://pinterest.com" rel="noopener noreferrer"&gt;Pinterest&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Then, and this is my personal favorite, go to &lt;a href="https://x.com" rel="noopener noreferrer"&gt;Twitter slash X&lt;/a&gt;, where a genuinely absurd amount of the best UI work gets posted before it lands anywhere else. &lt;/p&gt;

&lt;p&gt;Save screenshots. Save links to live sites too, since a real page tells the model way more than a flat image.&lt;/p&gt;

&lt;p&gt;Now, you &lt;em&gt;could&lt;/em&gt; just let those screenshots rot in a folder called &lt;code&gt;inspo_FINAL_v3&lt;/code&gt;. Or you could do the fun version and have Claude Code spin up a tiny inspiration library web app for you. &lt;/p&gt;

&lt;p&gt;Mine groups everything by design style, names the aesthetic in actual design vocabulary, and gives me keywords for each piece. &lt;/p&gt;

&lt;p&gt;Click a screenshot and I can copy an image prompt (for generating a matching hero background later) or copy a full brief (for generating the whole page).&lt;/p&gt;

&lt;p&gt;It turns a pile of pretty pictures into a reusable palette of &lt;em&gt;your&lt;/em&gt; taste.&lt;/p&gt;

&lt;p&gt;The point of this step is not to copy anyone. &lt;/p&gt;

&lt;p&gt;It is to give your future self a base to point at, so you are never starting from a blank prompt and an average result. &lt;/p&gt;

&lt;p&gt;You are starting from a direction you already know you like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Arm Claude Code with tools it does not ship with
&lt;/h2&gt;

&lt;p&gt;Out of the box, Claude Code is a strong developer with the visual judgment of a coin flip. &lt;/p&gt;

&lt;p&gt;So we hand it better eyes and better hands. Three categories are enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  A real design skill
&lt;/h3&gt;

&lt;p&gt;The default Anthropic frontend-design skill was a fine start, but at this point it kind of produces the slop it was meant to prevent. &lt;/p&gt;

&lt;p&gt;The one I reach for now is &lt;a href="https://impeccable.style" rel="noopener noreferrer"&gt;Impeccable&lt;/a&gt;, an open source Claude Code skill by Paul Bakaus. &lt;/p&gt;

&lt;p&gt;Instead of vaguely begging the model to "not look AI generated" (which never works), it does something clever: it &lt;em&gt;names the specific slop patterns&lt;/em&gt; and tells the model to avoid them.&lt;/p&gt;

&lt;p&gt;It ships as one skill with a stack of commands you invoke like slash commands. A few that I use constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/critique&lt;/code&gt; for an honest UX review of hierarchy and clarity&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/bolder&lt;/code&gt; to push a safe, boring design toward actual impact&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/quieter&lt;/code&gt; to pull an over caffeinated design back down&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/polish&lt;/code&gt; for a final alignment and spacing pass before shipping&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under the hood it hunts slop across seven areas: typography, color, spatial design, responsiveness, interaction, motion, and UX writing. &lt;/p&gt;

&lt;p&gt;There is even a Chrome extension that highlights slop patterns on a live site, which is a little bit rude and extremely useful. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://impeccable.style" rel="noopener noreferrer"&gt;before and after examples in the docs&lt;/a&gt; sell it better than I can.&lt;/p&gt;

&lt;p&gt;If Impeccable is not your flavor, there are other "taste" style skills in the same spirit floating around GitHub, and they work on the same principle.&lt;/p&gt;

&lt;h3&gt;
  
  
  An image and video MCP
&lt;/h3&gt;

&lt;p&gt;Claude Code cannot draw. It cannot generate that moody cinematic mountain range you want behind your hero text. &lt;/p&gt;

&lt;p&gt;So give it the ability with the &lt;a href="https://higgsfield.ai/mcp" rel="noopener noreferrer"&gt;Higgsfield MCP&lt;/a&gt;, which basically hands your agent a backstage pass to 30 plus image and video models through a single connection. &lt;/p&gt;

&lt;p&gt;You describe the hero image, it generates it, drops it into the section, done. No tab hopping to five other tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  A component registry
&lt;/h3&gt;

&lt;p&gt;For the smaller stuff, buttons, cards, pricing sections, pagination (when was the last time &lt;em&gt;you&lt;/em&gt; thought hard about pagination?), I browse &lt;a href="https://21st.dev" rel="noopener noreferrer"&gt;21st.dev&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;It is an open registry where each component ships with a "copy prompt" button. &lt;/p&gt;

&lt;p&gt;Paste that into Claude Code and it rebuilds the component inside your codebase, wired to your stack. &lt;/p&gt;

&lt;p&gt;Think of it as step one's taste hunting, but zoomed all the way in to the component level.&lt;/p&gt;

&lt;h3&gt;
  
  
  One warning before you go tool shopping
&lt;/h3&gt;

&lt;p&gt;Here is the trap, and I have fallen in it more than once. &lt;/p&gt;

&lt;p&gt;It is very easy to convince yourself you are one skill away from all your design problems being solved. You are not.&lt;/p&gt;

&lt;p&gt;You will find flashy skills that build gorgeous sites. &lt;/p&gt;

&lt;p&gt;The catch is they tend to be narrow and extremely prescriptive, so they hand you exactly one kind of output every time. &lt;/p&gt;

&lt;p&gt;That is not taste, that is a template with extra steps. &lt;/p&gt;

&lt;p&gt;I like Impeccable and Higgsfield precisely because they are &lt;em&gt;flexible&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;The downside of flexible is that they will not magically produce greatness. That still depends on your prompting and your taste. Which is the point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmc2ko5iiixwsacv1lltf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmc2ko5iiixwsacv1lltf.png" alt=" " width="360" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Build in loops, not in one shots
&lt;/h2&gt;

&lt;p&gt;This is the mindset shift that changed everything for me, so read it twice: &lt;strong&gt;stop trying to one shot.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The one shot is a lottery ticket. &lt;/p&gt;

&lt;p&gt;You type "make it premium," hit enter, and pray. &lt;/p&gt;

&lt;p&gt;Sometimes you win. Usually you get slop with a slightly nicer font. &lt;/p&gt;

&lt;p&gt;Instead, cast a very wide net up front, then narrow down.&lt;/p&gt;

&lt;p&gt;Here is the actual sequence I run:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79q7iip4x4g2p7exjtdg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79q7iip4x4g2p7exjtdg.png" alt=" " width="310" height="1892"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me make that concrete.&lt;/p&gt;

&lt;p&gt;Say I am building a landing page for a made up AI analytics startup called Kestrel, and the goal is to get people to book a demo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, five versions in five styles, all at once.&lt;/strong&gt; Not one, five. &lt;/p&gt;

&lt;p&gt;If you built that inspiration library from step one, you can just tell Claude Code to look at it, pull five aesthetic families, and build a page in each. &lt;/p&gt;

&lt;p&gt;Seeing them side by side on one screen makes it obvious which direction has legs, in a way that squinting at a terminal one attempt at a time never does. &lt;/p&gt;

&lt;p&gt;Say I land on a "vast quiet" minimalist look with a big cinematic image behind the hero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then, three variations of that one direction.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Same aesthetic, different body layouts. &lt;/p&gt;

&lt;p&gt;Maybe one is super vertical and minimal, one is a "ledger" style with an index that scrolls alongside you, one frames each section with edges. &lt;/p&gt;

&lt;p&gt;I pick the ledger, because that scrolling index is genuinely cool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then I nail the hero image before fussing with anything else.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I fire up the Higgsfield MCP, ask for four 2K options that fit the quiet aesthetic, and look at them in the actual hero slot. &lt;/p&gt;

&lt;p&gt;Too black and white? Ask for variations with a splash of color. &lt;/p&gt;

&lt;p&gt;That gentle golden "alpenglow" one? Sold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then transitions and weight.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I ask for the hero to fade into the body instead of hard cutting, and for elements to load in with a bit of heft so the whole thing feels premium rather than snappy and cheap.&lt;/p&gt;

&lt;p&gt;Notice what is happening. &lt;/p&gt;

&lt;p&gt;At every stage I am &lt;em&gt;seeing options and choosing&lt;/em&gt;, not blindly trusting a single generation. That is the whole trick.&lt;/p&gt;

&lt;h3&gt;
  
  
  The four things worth putting in your prompt
&lt;/h3&gt;

&lt;p&gt;You do not need a giant design doc. &lt;/p&gt;

&lt;p&gt;When those get too specific, they just produce the same thing every time, which is the slop problem wearing a fancier coat. &lt;/p&gt;

&lt;p&gt;I pass four things, loosely:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkk9ywvfwkhuyfrwaix82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkk9ywvfwkhuyfrwaix82.png" alt=" " width="760" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Aesthetic.&lt;/strong&gt; The general family of design you are going for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reference image.&lt;/strong&gt; This is where your taste library earns its keep. Drop a screenshot, or better, an actual URL of a site you like. You are matching a &lt;em&gt;feeling&lt;/em&gt;, not copying content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intent.&lt;/strong&gt; What is this and why does it exist? SaaS product? Event page? Who is the audience and what do you want them to do, read, click, or fill out?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guardrails.&lt;/strong&gt; The always and the never. This is your anti slop clause. "Never use purple gradients. Never use Inter. No 3D SaaS blobs."&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The secret weapon: a live tweak bar
&lt;/h3&gt;

&lt;p&gt;Even after all that, the page felt &lt;em&gt;almost&lt;/em&gt; right but a touch too minimal. &lt;/p&gt;

&lt;p&gt;Instead of guessing with more "make it pop" prompts, I asked Claude Code to add a live tweak bar to the dev server, the kind of panel you see inside Claude Design. &lt;/p&gt;

&lt;p&gt;Font family, font size, accent color, motion, reveal distance, anything where a design decision lives.&lt;/p&gt;

&lt;p&gt;Now I iterate by eye. &lt;/p&gt;

&lt;p&gt;I flip through fonts in real time instead of asking for ten rebuilt pages and comparing them like a lunatic.&lt;/p&gt;

&lt;p&gt;Being able to see differences instantly is the thing that finally gets you to something you actually like. And do not forget: references are not just for the hero. &lt;/p&gt;

&lt;p&gt;Feed the model URLs and screenshots for how you want the &lt;em&gt;body&lt;/em&gt; laid out too, since Claude Code can look at a live page and apply its structure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk9k6o6xmkjjxtwc3zpi3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk9k6o6xmkjjxtwc3zpi3.png" alt=" " width="360" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;None of this is a rulebook. Do not follow it to the letter, because the second something becomes a rigid formula, it starts producing its own flavor of slop. &lt;/p&gt;

&lt;p&gt;The point is a flexible workflow you can bend to any project:&lt;/p&gt;

&lt;p&gt;Cultivate taste so you have something to hand over. &lt;/p&gt;

&lt;p&gt;Arm your agent with a design skill, an image MCP, and a component registry.&lt;/p&gt;

&lt;p&gt;Then build in loops, wide net first, tweak by eye last, so you are never at the mercy of a single roll of the dice.&lt;/p&gt;

&lt;p&gt;The moat was never the toolbox. &lt;/p&gt;

&lt;p&gt;It was always your taste. &lt;/p&gt;

&lt;p&gt;Go build something that looks like &lt;em&gt;you&lt;/em&gt;, and leave the purple gradients where they belong.&lt;/p&gt;

&lt;p&gt;If you try this, I would love to see what you ship. Drop a link in the comments and let us compare notes.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>ai</category>
      <category>webdev</category>
      <category>claude</category>
      <category>design</category>
    </item>
    <item>
      <title>ratatop: the process table, and the parentheses that ruin everything</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Fri, 07 Aug 2026 18:21:29 +0000</pubDate>
      <link>https://dev.to/lovestaco/ratatop-the-process-table-and-the-parentheses-that-ruin-everything-13fn</link>
      <guid>https://dev.to/lovestaco/ratatop-the-process-table-and-the-parentheses-that-ruin-everything-13fn</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc?utm_source=ratatop" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;CPU, memory, disks and network were all "read a file, do some arithmetic, draw it". &lt;/p&gt;

&lt;p&gt;This one is different. &lt;/p&gt;

&lt;p&gt;It reads about 400 directories every tick, and it is the first box you can actually interact with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8yofzmsj87psuuzsmbc7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8yofzmsj87psuuzsmbc7.png" alt="ratatop" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is one bug in here that I would bet real money most &lt;code&gt;/proc&lt;/code&gt; parsers have shipped at some point. Let me start there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parentheses that ruin everything
&lt;/h2&gt;

&lt;p&gt;Here is a line from &lt;code&gt;/proc/[pid]/stat&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;125045 (cat) R 125025 125045 125025 0 -1 4194304 92 0 0 0 11 22 0 0 20 0 7 0 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Space separated. &lt;/p&gt;

&lt;p&gt;Field 1 is the pid, field 2 is the process name in parentheses, field 14 is user time, field 15 is system time, field 20 is thread count, field 24 is resident memory.&lt;/p&gt;

&lt;p&gt;So you split on whitespace and index into the result. Obvious. Works perfectly.&lt;/p&gt;

&lt;p&gt;Until someone runs a process called &lt;code&gt;my (weird) app&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;42 (my (weird) app) S 1 42 1 0 -1 0 0 0 0 0 5 5 0 0 20 0 3 0 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That name is three whitespace-separated tokens, so every field after it shifts by two. &lt;/p&gt;

&lt;p&gt;Your thread count is now reading someone's page fault counter. &lt;/p&gt;

&lt;p&gt;Your memory is reading a scheduling priority. &lt;/p&gt;

&lt;p&gt;Nothing crashes. &lt;/p&gt;

&lt;p&gt;The numbers are just quietly, confidently wrong.&lt;/p&gt;

&lt;p&gt;And the process name is fully user-controlled.&lt;/p&gt;

&lt;p&gt;Anyone can rename a thread to whatever they like.&lt;/p&gt;

&lt;p&gt;The fix is to not split the whole line at all.&lt;/p&gt;

&lt;p&gt;Find the &lt;strong&gt;last&lt;/strong&gt; closing parenthesis, take the name from between the first &lt;code&gt;(&lt;/code&gt; and that, and only then split what remains:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;parse_stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Stat&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="nf"&gt;.find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'('&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;close&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="nf"&gt;.rfind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;')'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Fields resume at `state`, which is field 3 in the man page's numbering.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nf"&gt;.split_whitespace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="nf"&gt;.parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;rfind&lt;/code&gt; instead of &lt;code&gt;find&lt;/code&gt;. That is the entire fix, and it is why the man page for &lt;code&gt;proc&lt;/code&gt; explicitly warns you about this field.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;number - 3&lt;/code&gt; offset is worth a look too. &lt;/p&gt;

&lt;p&gt;The man page numbers fields from 1, and the two we consumed manually were 1 and 2, so field 14 lives at index 11 of what is left. &lt;/p&gt;

&lt;p&gt;I wrote it as a closure that takes the man page's number directly, so the code reads &lt;code&gt;field(14)&lt;/code&gt; and matches the documentation rather than making you do arithmetic while reviewing it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Percent of what, exactly?
&lt;/h2&gt;

&lt;p&gt;Second decision that changes what the numbers mean.&lt;/p&gt;

&lt;p&gt;A process's CPU time is in clock ticks. To turn that into a percentage you need a denominator, and there are two reasonable choices:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Percent of one core.&lt;/strong&gt; A single-threaded process pinning a core shows 100%.&lt;/p&gt;

&lt;p&gt;A process using four cores shows 400%. This is what &lt;code&gt;htop&lt;/code&gt; does by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Percent of the whole machine.&lt;/strong&gt; That same core-pinning process shows 12.5% on my 8 core laptop, and the whole table sums to at most 100.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2xdub7lb8demo82klhu2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2xdub7lb8demo82klhu2.png" alt=" " width="784" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;btop defaults to the second, so that is what I did. The denominator is the total tick delta across every core, which I already had from the CPU box:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="py"&gt;.total.total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="nf"&gt;.saturating_sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.previous_total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Neither is wrong. But you have to pick one and say so, because "4.9" means very different things under each, and a user comparing your tool to htop will notice the factor of eight immediately.&lt;/p&gt;
&lt;h2&gt;
  
  
  The spike that isn't
&lt;/h2&gt;

&lt;p&gt;Subtle one, and it took me a moment to reason about rather than observe.&lt;/p&gt;

&lt;p&gt;CPU percentage is a delta between two samples. &lt;/p&gt;

&lt;p&gt;A process that started &lt;em&gt;since&lt;/em&gt; the last sample has no previous sample to subtract from.&lt;/p&gt;

&lt;p&gt;If you treat its absent baseline as zero, you compute its entire lifetime CPU time as if it were consumed in the last two seconds. &lt;/p&gt;

&lt;p&gt;Every freshly spawned process would flash at some enormous number, immediately sort to the top, and then drop back to reality on the following tick.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A process that appeared since the last sample has no baseline, and its&lt;/span&gt;
&lt;span class="c1"&gt;// lifetime total would read as a huge spike.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.previous&lt;/span&gt;&lt;span class="nf"&gt;.contains_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Reporting zero for one tick is a small lie. Reporting 60% for a process that just started is a much larger one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkwykacl5pspz4gxsswcp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkwykacl5pspz4gxsswcp.png" alt=" " width="360" height="244"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Sorting floats is not sorting
&lt;/h2&gt;

&lt;p&gt;Quick Rust one that catches everybody once.&lt;/p&gt;

&lt;p&gt;Every other column sorts with &lt;code&gt;sort_by_key&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;SortBy&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Memory&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.list&lt;/span&gt;&lt;span class="nf"&gt;.sort_by_key&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.memory&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;CPU is an &lt;code&gt;f64&lt;/code&gt;, and that does not compile. &lt;code&gt;sort_by_key&lt;/code&gt; requires &lt;code&gt;Ord&lt;/code&gt;, and floats only implement &lt;code&gt;PartialOrd&lt;/code&gt;, because &lt;code&gt;NaN&lt;/code&gt; compares false against everything including itself. &lt;/p&gt;

&lt;p&gt;A type where &lt;code&gt;x == x&lt;/code&gt; can be false cannot have a total order.&lt;/p&gt;

&lt;p&gt;So the CPU column needs the explicit version:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;SortBy&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Cpu&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.list&lt;/span&gt;&lt;span class="nf"&gt;.sort_by&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="py"&gt;.cpu&lt;/span&gt;&lt;span class="nf"&gt;.partial_cmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="py"&gt;.cpu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;cmp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Treating an incomparable pair as equal is the pragmatic call here. &lt;/p&gt;

&lt;p&gt;A &lt;code&gt;NaN&lt;/code&gt; percentage would be a bug upstream, and I would rather the row sit in an odd position than the whole table panic.&lt;/p&gt;
&lt;h2&gt;
  
  
  A stock widget, finally
&lt;/h2&gt;

&lt;p&gt;Four boxes in and I had not used a single ratatui widget that draws data. Everything was &lt;code&gt;Block&lt;/code&gt;, &lt;code&gt;Paragraph&lt;/code&gt;, and my own braille graph and meter.&lt;/p&gt;

&lt;p&gt;The process table finally has a use for one: &lt;code&gt;Scrollbar&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;ScrollbarState&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="nf"&gt;.saturating_sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nn"&gt;Scrollbar&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ScrollbarOrientation&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;VerticalRight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.begin_symbol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"↑"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;.end_symbol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"↓"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;.thumb_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.fg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HI_FG&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;.render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note that this one is a &lt;code&gt;StatefulWidget&lt;/code&gt;, not a &lt;code&gt;Widget&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Different trait, and it will not compile until you import it. &lt;/p&gt;

&lt;p&gt;The signature takes an extra &lt;code&gt;&amp;amp;mut State&lt;/code&gt; argument:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is ratatui's answer to a real problem with immediate mode. &lt;/p&gt;

&lt;p&gt;Some widgets need to remember something between frames, and if everything is rebuilt every frame there is nowhere to put it. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;StatefulWidget&lt;/code&gt; makes that state explicit and hands ownership to you, rather than hiding it inside the widget.&lt;/p&gt;

&lt;p&gt;Which raises the obvious question about my scroll position.&lt;/p&gt;
&lt;h2&gt;
  
  
  Scroll offset is derived, not stored
&lt;/h2&gt;

&lt;p&gt;The tempting design is a &lt;code&gt;scroll: usize&lt;/code&gt; field that you bump on every key press.&lt;/p&gt;

&lt;p&gt;I did not do that, and the reason is terminal resize. &lt;/p&gt;

&lt;p&gt;If you store the offset and the user makes the window shorter, the stored value can now point past the end of a shorter list, and the selection can end up off screen with no key press to trigger a correction.&lt;/p&gt;

&lt;p&gt;You end up writing clamping code that runs on resize, on list change, on selection change, and you will miss one.&lt;/p&gt;

&lt;p&gt;Instead the offset is computed fresh every frame from the two things that are actually true:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;scroll_offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;last_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;selected&lt;/span&gt;&lt;span class="nf"&gt;.saturating_sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A pure function of selection, viewport height and list length.&lt;/p&gt;

&lt;p&gt;Resize the terminal and it just produces the right answer, because it never had a stale answer to begin with. &lt;/p&gt;

&lt;p&gt;This is immediate mode paying off, and it is a pattern I would not have reached for coming from a retained UI.&lt;/p&gt;

&lt;p&gt;The selection index still has to be stored, and it still needs clamping, because processes exit constantly and the list genuinely shrinks under the cursor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzli69nambgwh4hv5wwbx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzli69nambgwh4hv5wwbx.png" alt=" " width="759" height="360"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Small things that add up
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Kernel threads have an empty &lt;code&gt;cmdline&lt;/code&gt;.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;That is how you tell them apart from real processes. &lt;/p&gt;

&lt;p&gt;btop shows their &lt;code&gt;comm&lt;/code&gt; in brackets instead, so &lt;code&gt;[kworker/4:0-events]&lt;/code&gt; is a rendering convention, not something the kernel gives you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;comm&lt;/code&gt; is truncated to 15 bytes by the kernel.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;That is why &lt;code&gt;codebase-memory&lt;/code&gt; shows as &lt;code&gt;codebase-memor&lt;/code&gt; in the Program column. &lt;/p&gt;

&lt;p&gt;Not my bug, and there is nothing to fix: the full name is in the Command column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;uid to username needs &lt;code&gt;/etc/passwd&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;/proc/[pid]&lt;/code&gt; gives you a numeric uid via the directory's owner. &lt;/p&gt;

&lt;p&gt;I read the passwd file once at startup rather than per process, because 400 processes times one file read per tick is a lot of pointless syscalls for a mapping that does not change while the tool is open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything can vanish mid-read.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Between listing &lt;code&gt;/proc&lt;/code&gt; and opening &lt;code&gt;/proc/1234/stat&lt;/code&gt;, pid 1234 may well have exited. Every failure in that path just means "gone" and skips the entry. &lt;/p&gt;

&lt;p&gt;No error, no log, no retry.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this cost
&lt;/h2&gt;

&lt;p&gt;Worth being honest about the performance side.&lt;/p&gt;

&lt;p&gt;The other four boxes read a handful of files per tick. &lt;/p&gt;

&lt;p&gt;This one opens roughly 1200 files per tick: &lt;code&gt;stat&lt;/code&gt;, &lt;code&gt;cmdline&lt;/code&gt; and a &lt;code&gt;metadata&lt;/code&gt; call for each of about 400 processes.&lt;/p&gt;

&lt;p&gt;My test suite went from about one second to eleven, because several tests construct a full &lt;code&gt;App&lt;/code&gt; and that now includes a complete process scan.&lt;/p&gt;

&lt;p&gt;It is fine at a 2 second refresh.&lt;/p&gt;

&lt;p&gt;It would not be fine at 100ms, which my &lt;code&gt;+&lt;/code&gt; key allows. &lt;/p&gt;

&lt;p&gt;btop solves this with the "lazy" mode you can see in my top-right corner, updating the expensive columns less often than the cheap ones. &lt;/p&gt;

&lt;p&gt;I have the label but not yet the behaviour, which is a slightly embarrassing thing to leave in the UI.&lt;/p&gt;

&lt;p&gt;That is on the list.&lt;/p&gt;
&lt;h2&gt;
  
  
  All four boxes, done
&lt;/h2&gt;

&lt;p&gt;Five days, four boxes, one Rust TUI that reads real system state:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/lovestaco" rel="noopener noreferrer"&gt;
        lovestaco
      &lt;/a&gt; / &lt;a href="https://github.com/lovestaco/ratatop" rel="noopener noreferrer"&gt;
        ratatop
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Keep an eye on what's cooking in your system 
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/lovestaco/ratatop/media/cover.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flovestaco%2Fratatop%2FHEAD%2Fmedia%2Fcover.png" alt="ratatop" width="100%"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;A reimplementation of &lt;a href="https://github.com/aristocratos/btop" rel="noopener noreferrer"&gt;btop&lt;/a&gt; in Rust, built on
&lt;a href="https://ratatui.rs" rel="nofollow noopener noreferrer"&gt;ratatui&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The goal is a resource monitor that feels like btop: the four-box layout, braille
graphs with gradient fills, mouse-driven navigation, and a themable process
manager, but written as a modern Rust TUI.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Status&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The CPU box works.&lt;/strong&gt; It samples real data and renders btop's layout:&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/lovestaco/ratatop/media/ratatop.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flovestaco%2Fratatop%2FHEAD%2Fmedia%2Fratatop.png" alt="ratatop running"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Per-core braille history graphs, a gradient load bar with sub-cell resolution
frequency, package temperature, battery, and load averages.&lt;/p&gt;
&lt;p&gt;Everything else, the memory, network, and process boxes, is still to come.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Design notes&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data source:&lt;/strong&gt; &lt;code&gt;/proc&lt;/code&gt; and &lt;code&gt;/sys&lt;/code&gt; on Linux first, read directly rather than
shelling out. btop's &lt;code&gt;src/linux/btop_collect.cpp&lt;/code&gt; is the reference for what to
read and how often; the BSD/macOS collectors are out of scope for v1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graphs are the hard part.&lt;/strong&gt; btop's look comes from braille sub-cell plotting
with a color gradient applied along the value axis, not from a stock chart
widget. Expect…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/lovestaco/ratatop" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;From here it is polish rather than new territory. &lt;/p&gt;

&lt;p&gt;Filtering the process list, killing and renicing, tree view, theme files, mouse support.&lt;/p&gt;

&lt;p&gt;The thing I did not expect from this week is how much of the work was &lt;strong&gt;reading kernel documentation carefully&lt;/strong&gt; rather than writing Rust. &lt;/p&gt;

&lt;p&gt;The parentheses in &lt;code&gt;comm&lt;/code&gt;, the 512 byte sectors, &lt;code&gt;f_bavail&lt;/code&gt; versus &lt;code&gt;f_bfree&lt;/code&gt;, &lt;code&gt;MemAvailable&lt;/code&gt; versus &lt;code&gt;MemFree&lt;/code&gt;, transmit bytes being field nine. &lt;/p&gt;

&lt;p&gt;Every box had exactly one trap like that, and every one of them fails silently with a plausible looking number rather than an error.&lt;/p&gt;

&lt;p&gt;Which is the actual lesson, I think. &lt;/p&gt;

&lt;p&gt;When you are reading a kernel interface, a wrong answer looks exactly like a right answer. &lt;/p&gt;

&lt;p&gt;The only defence is reading the docs properly and writing a test that encodes what you learned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feuw163f4gzdx4cnrf8ea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feuw163f4gzdx4cnrf8ea.png" alt=" " width="360" height="196"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>rust</category>
      <category>tui</category>
      <category>cli</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Google Quietly Dropped 12 Free AI Tools. Developers Should Probably Care.</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Thu, 06 Aug 2026 18:17:02 +0000</pubDate>
      <link>https://dev.to/lovestaco/google-quietly-dropped-12-free-ai-tools-developers-should-probably-care-45cf</link>
      <guid>https://dev.to/lovestaco/google-quietly-dropped-12-free-ai-tools-developers-should-probably-care-45cf</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc?utm_source=ratatop" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A few years ago the AI conversation looked like this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Should I pay $20?"&lt;/p&gt;

&lt;p&gt;"No, $200."&lt;/p&gt;

&lt;p&gt;"Actually this new tool is $39/month."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My wallet started looking like it had gone through a startup funding winter.&lt;/p&gt;

&lt;p&gt;Then Google quietly walked into the room and started dropping free AI tools like Oprah handing out cars.&lt;/p&gt;

&lt;p&gt;"You get an AI IDE!"&lt;br&gt;
"You get a workflow builder!"&lt;br&gt;
"You get a GitHub coding agent!"&lt;/p&gt;

&lt;p&gt;...except nobody really noticed because Google announced them across five different events, Labs pages, GitHub repos, and random blog posts.&lt;/p&gt;

&lt;p&gt;So I spent some time collecting the ones developers will actually find useful.&lt;/p&gt;

&lt;p&gt;No "AI that writes your wedding speech."&lt;/p&gt;

&lt;p&gt;No "AI that guesses your spirit animal."&lt;/p&gt;

&lt;p&gt;Just tools that can actually help you ship software.&lt;/p&gt;

&lt;p&gt;Bookmark this one.  &lt;/p&gt;
&lt;h2&gt;
  
  
  1. Pomelli
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://labs.google/pomelli" rel="noopener noreferrer"&gt;https://labs.google/pomelli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flbhm9abso05u0wy2cxye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flbhm9abso05u0wy2cxye.png" alt="pomelli" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've ever launched a side project, you already know the painful truth.&lt;/p&gt;

&lt;p&gt;Building the product is fun.&lt;/p&gt;

&lt;p&gt;Writing 37 LinkedIn posts explaining the product... not so much.&lt;/p&gt;

&lt;p&gt;Pomelli takes your website, understands what your product does, builds a brand profile, then generates social posts around it.&lt;/p&gt;

&lt;p&gt;Think of it as hiring an intern that actually reads your landing page before tweeting.&lt;/p&gt;

&lt;p&gt;Would I let it post automatically?&lt;/p&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;Would I happily let it generate the first draft so I don't stare at a blinking cursor?&lt;/p&gt;

&lt;p&gt;Absolutely.&lt;/p&gt;

&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Indie hackers&lt;/li&gt;
&lt;li&gt;SaaS founders&lt;/li&gt;
&lt;li&gt;Open-source maintainers pretending they enjoy marketing&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  2. Stitch
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://stitch.withgoogle.com" rel="noopener noreferrer"&gt;https://stitch.withgoogle.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkj63rruk3pudaz86myox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkj63rruk3pudaz86myox.png" alt=" " width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remember when designing an app meant opening Figma...&lt;/p&gt;

&lt;p&gt;...moving a button 3 pixels...&lt;/p&gt;

&lt;p&gt;...asking for feedback...&lt;/p&gt;

&lt;p&gt;...moving it back 3 pixels?&lt;/p&gt;

&lt;p&gt;Stitch skips a surprising amount of that.&lt;/p&gt;

&lt;p&gt;You describe the interface.&lt;/p&gt;

&lt;p&gt;Or upload a sketch.&lt;/p&gt;

&lt;p&gt;Or even paste a wireframe.&lt;/p&gt;

&lt;p&gt;It generates modern UI designs and can even produce frontend code.&lt;/p&gt;

&lt;p&gt;It's not replacing designers.&lt;/p&gt;

&lt;p&gt;It's replacing the blank canvas.&lt;/p&gt;

&lt;p&gt;That's a huge difference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5nkw9cxbdc5wwsl957ux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5nkw9cxbdc5wwsl957ux.png" alt=" " width="360" height="360"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Antigravity
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://antigravity.google/" rel="noopener noreferrer"&gt;https://antigravity.google/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0zsh4d3meizqy7pw4gqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0zsh4d3meizqy7pw4gqw.png" alt=" " width="799" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every AI company apparently wakes up and decides:&lt;/p&gt;

&lt;p&gt;"You know what? Let's build another code editor."&lt;/p&gt;

&lt;p&gt;Google joined the party with Antigravity.&lt;/p&gt;

&lt;p&gt;Prompt it.&lt;/p&gt;

&lt;p&gt;Generate an app.&lt;/p&gt;

&lt;p&gt;Modify code.&lt;/p&gt;

&lt;p&gt;Refactor.&lt;/p&gt;

&lt;p&gt;Debug.&lt;/p&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;But competition here is fantastic.&lt;/p&gt;

&lt;p&gt;Cursor, Claude Code, Windsurf and now Google all trying to out-build each other means developers keep winning.&lt;/p&gt;

&lt;p&gt;I'm perfectly okay being the spoiled child in this particular custody battle.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Mixboard
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://labs.google/mixboard" rel="noopener noreferrer"&gt;https://labs.google/mixboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc4m8w2441uo9amvtutio.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc4m8w2441uo9amvtutio.png" alt=" " width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imagine Canva and Pinterest had an AI-powered child.&lt;/p&gt;

&lt;p&gt;That's Mixboard.&lt;/p&gt;

&lt;p&gt;Instead of generating one image...&lt;/p&gt;

&lt;p&gt;...you generate multiple concepts.&lt;/p&gt;

&lt;p&gt;Mix.&lt;/p&gt;

&lt;p&gt;Match.&lt;/p&gt;

&lt;p&gt;Replace.&lt;/p&gt;

&lt;p&gt;Rearrange.&lt;/p&gt;

&lt;p&gt;Build moodboards.&lt;/p&gt;

&lt;p&gt;For landing pages, presentations, branding, thumbnails, or app inspiration, it's surprisingly useful.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Disco
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://labs.google/disco" rel="noopener noreferrer"&gt;https://labs.google/disco&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpuxkhc08pttloc4uomn4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpuxkhc08pttloc4uomn4.png" alt=" " width="799" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This one's still on the waitlist.&lt;/p&gt;

&lt;p&gt;The idea is interesting though.&lt;/p&gt;

&lt;p&gt;Instead of opening 15 tabs and manually connecting information, Disco understands what's already in your browser and lets AI interact with everything together.&lt;/p&gt;

&lt;p&gt;Think NotebookLM...&lt;/p&gt;

&lt;p&gt;...except your browser becomes the notebook.&lt;/p&gt;

&lt;p&gt;I'm curious to see where this one goes.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. NotebookLM
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://notebooklm.google" rel="noopener noreferrer"&gt;https://notebooklm.google&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp9zhcswg7wtm7sugnfa5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp9zhcswg7wtm7sugnfa5.png" alt=" " width="799" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This one deserves way more attention.&lt;/p&gt;

&lt;p&gt;Feed it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDFs&lt;/li&gt;
&lt;li&gt;Docs&lt;/li&gt;
&lt;li&gt;YouTube videos&lt;/li&gt;
&lt;li&gt;Notes&lt;/li&gt;
&lt;li&gt;Research papers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then ask questions.&lt;/p&gt;

&lt;p&gt;Generate summaries.&lt;/p&gt;

&lt;p&gt;Create study guides.&lt;/p&gt;

&lt;p&gt;Even generate podcast-style conversations.&lt;/p&gt;

&lt;p&gt;I've thrown technical RFCs into NotebookLM before.&lt;/p&gt;

&lt;p&gt;Reading 80 pages?&lt;/p&gt;

&lt;p&gt;No thanks.&lt;/p&gt;

&lt;p&gt;Asking "Explain this architecture like I'm joining the team tomorrow"?&lt;/p&gt;

&lt;p&gt;Now we're talking.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Learn Your Way
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://learnyourway.withgoogle.com/" rel="noopener noreferrer"&gt;https://learnyourway.withgoogle.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft4th7ss1xh34d2yep329.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft4th7ss1xh34d2yep329.png" alt=" " width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This feels like Google quietly built a personalized teacher.&lt;/p&gt;

&lt;p&gt;Pick a topic.&lt;/p&gt;

&lt;p&gt;It creates a learning path around &lt;em&gt;you&lt;/em&gt;, not around a generic syllabus.&lt;/p&gt;

&lt;p&gt;Want to learn Kubernetes?&lt;/p&gt;

&lt;p&gt;It builds the course.&lt;/p&gt;

&lt;p&gt;Rust?&lt;/p&gt;

&lt;p&gt;Same.&lt;/p&gt;

&lt;p&gt;Machine Learning?&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;It's surprisingly refreshing compared to watching fifteen random YouTube videos hoping they eventually connect.&lt;/p&gt;
&lt;h2&gt;
  
  
  8. Flow Music
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.flowmusic.app/" rel="noopener noreferrer"&gt;https://www.flowmusic.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwz0v57i98mk0wyff1v5d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwz0v57i98mk0wyff1v5d.png" alt=" " width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not exactly a developer tool.&lt;/p&gt;

&lt;p&gt;Still cool.&lt;/p&gt;

&lt;p&gt;You describe the music.&lt;/p&gt;

&lt;p&gt;AI produces songs.&lt;/p&gt;

&lt;p&gt;Even music videos.&lt;/p&gt;

&lt;p&gt;If you've ever wanted your side project launch trailer to sound slightly more cinematic than royalty-free ukulele music...&lt;/p&gt;

&lt;p&gt;here you go.&lt;/p&gt;
&lt;h2&gt;
  
  
  9. Google AI Studio
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aistudio.google.com/" rel="noopener noreferrer"&gt;https://aistudio.google.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj3d66k12sve6vetyik9m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj3d66k12sve6vetyik9m.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is probably the tool developers should spend the most time with.&lt;/p&gt;

&lt;p&gt;Prototype prompts.&lt;/p&gt;

&lt;p&gt;Test Gemini models.&lt;/p&gt;

&lt;p&gt;Build AI apps.&lt;/p&gt;

&lt;p&gt;Experiment with structured outputs.&lt;/p&gt;

&lt;p&gt;Play with APIs.&lt;/p&gt;

&lt;p&gt;Break things.&lt;/p&gt;

&lt;p&gt;Fix them.&lt;/p&gt;

&lt;p&gt;Repeat.&lt;/p&gt;

&lt;p&gt;Think of it as Google's playground where ideas become prototypes ridiculously fast.&lt;/p&gt;
&lt;h2&gt;
  
  
  10. Jules
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jules.google/" rel="noopener noreferrer"&gt;https://jules.google/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb0cwnuom02genopretzb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb0cwnuom02genopretzb.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This one feels like hiring an eager junior developer.&lt;/p&gt;

&lt;p&gt;Connect GitHub.&lt;/p&gt;

&lt;p&gt;Assign work.&lt;/p&gt;

&lt;p&gt;It creates branches.&lt;/p&gt;

&lt;p&gt;Implements changes.&lt;/p&gt;

&lt;p&gt;Opens pull requests.&lt;/p&gt;

&lt;p&gt;It won't replace senior engineers.&lt;/p&gt;

&lt;p&gt;But handing it repetitive tasks?&lt;/p&gt;

&lt;p&gt;That's where it gets interesting.&lt;/p&gt;

&lt;p&gt;Especially for bug fixes, migrations and mechanical refactors.&lt;/p&gt;
&lt;h2&gt;
  
  
  11. Gemini CLI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/google-gemini/gemini-cli" rel="noopener noreferrer"&gt;https://github.com/google-gemini/gemini-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuw5tv9x0b2e27nw28wvy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuw5tv9x0b2e27nw28wvy.png" alt=" " width="799" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your happy place looks like this...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git
vim
tmux
terminal
coffee
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;...Gemini CLI belongs on your machine.&lt;/p&gt;

&lt;p&gt;Run commands.&lt;/p&gt;

&lt;p&gt;Generate code.&lt;/p&gt;

&lt;p&gt;Explain repositories.&lt;/p&gt;

&lt;p&gt;Execute workflows.&lt;/p&gt;

&lt;p&gt;All without leaving the terminal.&lt;/p&gt;

&lt;p&gt;Because apparently opening browsers is becoming optional now.&lt;/p&gt;
&lt;h2&gt;
  
  
  12. Codewiki
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://codewiki.google/" rel="noopener noreferrer"&gt;https://codewiki.google/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsel7222r023zkcm1uvmz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsel7222r023zkcm1uvmz.png" alt=" " width="799" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open a repository.&lt;/p&gt;

&lt;p&gt;Instead of manually reading fifty folders...&lt;/p&gt;

&lt;p&gt;...you get a wiki explaining the architecture.&lt;/p&gt;

&lt;p&gt;How components connect.&lt;/p&gt;

&lt;p&gt;What each module does.&lt;/p&gt;

&lt;p&gt;Why things exist.&lt;/p&gt;

&lt;p&gt;If you've ever cloned an unfamiliar repository and immediately typed:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;where do I even start?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is your answer.&lt;/p&gt;

&lt;p&gt;(If you've used DeepWiki before, you'll immediately understand the appeal.)&lt;/p&gt;
&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;The interesting part isn't that Google released twelve AI tools.&lt;/p&gt;

&lt;p&gt;It's that they're covering almost every stage of building software.&lt;/p&gt;

&lt;p&gt;Need ideas?&lt;/p&gt;

&lt;p&gt;NotebookLM.&lt;/p&gt;

&lt;p&gt;Need UI?&lt;/p&gt;

&lt;p&gt;Stitch.&lt;/p&gt;

&lt;p&gt;Need code?&lt;/p&gt;

&lt;p&gt;Antigravity or GeminiCLI.&lt;/p&gt;

&lt;p&gt;Need automation?&lt;/p&gt;

&lt;p&gt;Opal.&lt;/p&gt;

&lt;p&gt;Need marketing?&lt;/p&gt;

&lt;p&gt;Pomelli.&lt;/p&gt;

&lt;p&gt;For a company that used to be "the search engine," Google suddenly looks a lot more like an end-to-end developer platform.&lt;/p&gt;

&lt;p&gt;The AI race isn't just about building smarter models anymore.&lt;/p&gt;

&lt;p&gt;It's becoming a battle over who owns your entire workflow.&lt;/p&gt;

&lt;p&gt;And as developers...&lt;/p&gt;

&lt;p&gt;...we get to steal the best parts from everyone.&lt;/p&gt;

&lt;p&gt;So yes, bookmark this list.&lt;/p&gt;

&lt;p&gt;Six months from now you'll probably remember that "Google had that one free tool..."&lt;/p&gt;

&lt;p&gt;Now you'll know exactly which one.&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>ratatop: the network box, and why your ISP lies with units</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Sat, 01 Aug 2026 16:48:16 +0000</pubDate>
      <link>https://dev.to/lovestaco/ratatop-the-network-box-and-why-your-isp-lies-with-units-4lpo</link>
      <guid>https://dev.to/lovestaco/ratatop-the-network-box-and-why-your-isp-lies-with-units-4lpo</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc?utm_source=ratatop" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Day four of building a btop-style system monitor in Rust with &lt;a href="https://ratatui.rs" rel="noopener noreferrer"&gt;ratatui&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;CPU, memory, disks are done. Today the network box lands, which means three of btop's four boxes are up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fojdhy36dxh4kipg085zm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fojdhy36dxh4kipg085zm.png" alt=" " width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three things in there took actual thought. &lt;/p&gt;

&lt;p&gt;The unit situation, the shared scale, and the fact that the upload graph is upside down on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Byte/s and Kibps, side by side
&lt;/h2&gt;

&lt;p&gt;Look at that first row again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▼ 566 Byte/s  (4.42 Kibps)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Same number. Twice. Different units.&lt;/p&gt;

&lt;p&gt;btop does this deliberately, and once you notice why, you cannot unsee it.&lt;/p&gt;

&lt;p&gt;Your ISP sells you bandwidth in &lt;strong&gt;bits&lt;/strong&gt; per second. &lt;/p&gt;

&lt;p&gt;100 Mbps, 300 Mbps, whatever the sticker says.&lt;/p&gt;

&lt;p&gt;Your download manager, your browser, and every file transfer you have ever watched report progress in &lt;strong&gt;bytes&lt;/strong&gt; per second.&lt;/p&gt;

&lt;p&gt;The factor between them is 8.&lt;/p&gt;

&lt;p&gt;So the "100 Mbps" plan you pay for tops out around 12.5 MB/s in the progress bar, and every few months someone concludes their internet is broken when it is in fact working exactly as sold.&lt;/p&gt;

&lt;p&gt;btop refuses to pick a side. &lt;/p&gt;

&lt;p&gt;It shows both, so you can compare against your plan and against your download at the same time without doing mental arithmetic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4uo8rw5pbbdmmhzbwca0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4uo8rw5pbbdmmhzbwca0.png" alt=" " width="360" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The conversion in code is unremarkable, which is the point:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;format_bits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes_per_sec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;UNITS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"bps"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Kibps"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Mibps"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Gibps"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes_per_sec&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1024.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UNITS&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;decimals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"({value:.decimals$} {})"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UNITS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note &lt;code&gt;Kibps&lt;/code&gt; and not &lt;code&gt;Kbps&lt;/code&gt;. Dividing by 1024 gives you kibibits; dividing by 1000 gives you kilobits. &lt;/p&gt;

&lt;p&gt;btop uses 1024, so the label has to say so. &lt;/p&gt;

&lt;p&gt;Getting this wrong is a 2.4% error that nobody would ever notice and that would still be wrong.&lt;/p&gt;
&lt;h2&gt;
  
  
  A column that jitters is a column you cannot read
&lt;/h2&gt;

&lt;p&gt;Small thing I got wrong and had to fix.&lt;/p&gt;

&lt;p&gt;My first version formatted rates with a fixed two decimals. &lt;/p&gt;

&lt;p&gt;Perfectly reasonable, and it produced this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▼ Top:  (4.42 Kibps)
▼ Top:  (43.80 Kibps)
▼ Top:  (438.00 Kibps)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The number changes width as traffic grows, so the whole column shuffles sideways every time the value crosses a power of ten. &lt;/p&gt;

&lt;p&gt;In a display that redraws twice a second, that is genuinely distracting.&lt;/p&gt;

&lt;p&gt;The fix is the same three-significant-figure rule the memory box already used:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;4.42&lt;/code&gt;, &lt;code&gt;43.8&lt;/code&gt;, &lt;code&gt;438&lt;/code&gt;. Always three digits. Always the same width.&lt;/p&gt;

&lt;p&gt;This is the second time in this project I have written that exact function, which is usually a sign it should be shared. &lt;/p&gt;

&lt;p&gt;For now it lives in both places because the memory box counts bytes and this one counts bits, and I would rather have two small honest copies than one abstraction that takes four parameters to explain itself.&lt;/p&gt;
&lt;h2&gt;
  
  
  The upload graph is upside down on purpose
&lt;/h2&gt;

&lt;p&gt;The two graphs on the left are download on top and upload underneath, and the bottom one is mirrored: it hangs from its top edge and grows downward.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD
    subgraph BOX[" "]
        D["download graph&amp;lt;br/&amp;gt;grows up from the middle"]
        M["shared baseline"]
        U["upload graph&amp;lt;br/&amp;gt;grows down from the middle"]
    end
    D --- M
    M --- U
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It reads as one figure with traffic flowing outward from a centre line, rather than two unrelated charts stacked by coincidence.&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;BrailleGraph&lt;/code&gt; widget from day one only knew how to fill upward from the bottom, so this needed a new option. &lt;/p&gt;

&lt;p&gt;The change turned out to be four lines, because the only thing that differs is which end you count the dot rows from:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.inverted&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dot&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dot_rows&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is the whole feature. Everything else, the braille bit packing, the gradient, the two-samples-per-cell trick, is unchanged.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffg4gke727s07y7ixs84z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffg4gke727s07y7ixs84z.png" alt=" " width="360" height="408"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Both graphs must share one scale
&lt;/h2&gt;

&lt;p&gt;Here is the part that is easy to get wrong and produces a chart that actively lies.&lt;/p&gt;

&lt;p&gt;Each direction has its own peak. &lt;/p&gt;

&lt;p&gt;The obvious move is to scale each graph to its own maximum, so both always use the full height of their box.&lt;/p&gt;

&lt;p&gt;Do that and you get a display where a 3 KB/s upload trickle and a 3 MB/s download flood are drawn at &lt;strong&gt;exactly the same height&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Two graphs, sitting adjacent, implying a comparison that is off by a factor of a thousand.&lt;/p&gt;

&lt;p&gt;So the scale is shared, computed once from whichever direction peaked higher:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;graph_max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.download.top&lt;/span&gt;&lt;span class="nf"&gt;.max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.upload.top&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1024.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Two details worth pointing out.&lt;/p&gt;

&lt;p&gt;It scales to the &lt;strong&gt;peak seen&lt;/strong&gt;, not the current value. &lt;/p&gt;

&lt;p&gt;Scaling to the current value means the graph rescales every frame, so a flat line and a spike look identical and the shape carries no information at all.&lt;/p&gt;

&lt;p&gt;And there is a 10 KiB floor. &lt;/p&gt;

&lt;p&gt;Without it, an idle interface scales to its own noise, and three stray bytes of ARP traffic render as a mountain range. &lt;/p&gt;

&lt;p&gt;The floor is why the label in my screenshot reads &lt;code&gt;20K&lt;/code&gt; even though nothing much is happening.&lt;/p&gt;
&lt;h2&gt;
  
  
  There is no file for your IP address
&lt;/h2&gt;

&lt;p&gt;Day three needed a syscall because filesystem usage has no file. Today the same thing happened again, and I did not expect it to.&lt;/p&gt;

&lt;p&gt;The rates all come from &lt;code&gt;/proc/net/dev&lt;/code&gt;. Easy. But the &lt;code&gt;192.168.29.14&lt;/code&gt; in the title has no file behind it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/proc/net/route&lt;/code&gt; gives you the gateway, not your address. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;/proc/net/fib_trie&lt;/code&gt; contains local addresses, but it is a serialised routing trie, not an interface listing, and parsing it is exactly as unpleasant as that sounds. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;/sys/class/net/wlo1/&lt;/code&gt; has the MAC, the MTU, the link speed, and no IP.&lt;/p&gt;

&lt;p&gt;So it is &lt;code&gt;getifaddrs(3)&lt;/code&gt;, and my second &lt;code&gt;unsafe&lt;/code&gt; block.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ifaddrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;null_mut&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getifaddrs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="nf"&gt;.is_null&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;*&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.ifa_next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;freeifaddrs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This one is chunkier than day three's &lt;code&gt;statvfs&lt;/code&gt;, because it is a linked list. You walk it, you filter to &lt;code&gt;AF_INET&lt;/code&gt; because an interface carries several addresses and only IPv4 fits the box, you cast a generic &lt;code&gt;sockaddr&lt;/code&gt; to a &lt;code&gt;sockaddr_in&lt;/code&gt; to reach the actual four bytes, and you free the list exactly once through the same pointer you were handed.&lt;/p&gt;

&lt;p&gt;The line I am most pleased with is this one:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.ifa_next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It advances the cursor &lt;strong&gt;before&lt;/strong&gt; the body of the loop touches anything. Do it at the bottom instead and every &lt;code&gt;continue&lt;/code&gt; in the loop becomes an infinite loop. There are two &lt;code&gt;continue&lt;/code&gt;s in there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Field nine, not field two
&lt;/h2&gt;

&lt;p&gt;A parsing trap for anyone reading &lt;code&gt;/proc/net/dev&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inter-|   Receive                                     |  Transmit
 face |bytes  packets errs drop fifo frame compressed multicast|bytes packets ...
  wlo1: 319815680  240000    0    0    0     0     0    0  137363456  180000 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Received bytes is the first counter. &lt;/p&gt;

&lt;p&gt;Transmitted bytes is the &lt;strong&gt;ninth&lt;/strong&gt;, because receive gets eight columns before transmit starts. &lt;/p&gt;

&lt;p&gt;Grab field 1 for upload and you will be reporting received packets as your upload speed, and it will look plausible enough that you might not catch it.&lt;/p&gt;

&lt;p&gt;The two header lines take care of themselves, incidentally. &lt;/p&gt;

&lt;p&gt;Neither contains a colon, and the parser splits each line on the colon to separate the name from the counters, so they drop out before any field indexing happens.&lt;/p&gt;

&lt;p&gt;I originally wrote a comment claiming the header needed special handling, then actually checked, and it does not. &lt;/p&gt;

&lt;p&gt;Worth checking your own comments occasionally.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cycling interfaces
&lt;/h2&gt;

&lt;p&gt;btop puts &lt;code&gt;&amp;lt;b wlo1 n&amp;gt;&lt;/code&gt; in the corner, which is both a label and a keymap: &lt;code&gt;b&lt;/code&gt; for the previous interface, &lt;code&gt;n&lt;/code&gt; for the next.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;KeyCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="py"&gt;.net&lt;/span&gt;&lt;span class="nf"&gt;.cycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="nn"&gt;KeyCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="py"&gt;.net&lt;/span&gt;&lt;span class="nf"&gt;.cycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Loopback is filtered out of the list. &lt;/p&gt;

&lt;p&gt;It is always present, always busy, and never the thing you opened a monitor to look at.&lt;/p&gt;

&lt;p&gt;The default selection comes from &lt;code&gt;/proc/net/route&lt;/code&gt;: whichever interface carries the route with a destination of all zeroes is the default route, and that is almost always the one you care about. &lt;/p&gt;

&lt;p&gt;If that lookup fails, it falls back to whichever interface has moved the most bytes.&lt;/p&gt;
&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Processes. The last box, and by a distance the biggest.&lt;/p&gt;

&lt;p&gt;Reading &lt;code&gt;/proc/[pid]/stat&lt;/code&gt; for every process on every tick, computing per-process CPU deltas, sorting by any column, and making the list scroll with a selection cursor. &lt;/p&gt;

&lt;p&gt;htop and btop both have a lot of hard-won edge case handling in that area, so I expect to be reading their source more than usual.&lt;/p&gt;

&lt;p&gt;Code so far:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/lovestaco" rel="noopener noreferrer"&gt;
        lovestaco
      &lt;/a&gt; / &lt;a href="https://github.com/lovestaco/ratatop" rel="noopener noreferrer"&gt;
        ratatop
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Keep an eye on what's cooking in your system 
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/lovestaco/ratatop/media/cover.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flovestaco%2Fratatop%2FHEAD%2Fmedia%2Fcover.png" alt="ratatop" width="100%"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;A reimplementation of &lt;a href="https://github.com/aristocratos/btop" rel="noopener noreferrer"&gt;btop&lt;/a&gt; in Rust, built on
&lt;a href="https://ratatui.rs" rel="nofollow noopener noreferrer"&gt;ratatui&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The goal is a resource monitor that feels like btop: the four-box layout, braille
graphs with gradient fills, mouse-driven navigation, and a themable process
manager, but written as a modern Rust TUI.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Status&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The CPU box works.&lt;/strong&gt; It samples real data and renders btop's layout:&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/lovestaco/ratatop/media/ratatop.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flovestaco%2Fratatop%2FHEAD%2Fmedia%2Fratatop.png" alt="ratatop running"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Per-core braille history graphs, a gradient load bar with sub-cell resolution
frequency, package temperature, battery, and load averages.&lt;/p&gt;
&lt;p&gt;Everything else, the memory, network, and process boxes, is still to come.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Design notes&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data source:&lt;/strong&gt; &lt;code&gt;/proc&lt;/code&gt; and &lt;code&gt;/sys&lt;/code&gt; on Linux first, read directly rather than
shelling out. btop's &lt;code&gt;src/linux/btop_collect.cpp&lt;/code&gt; is the reference for what to
read and how often; the BSD/macOS collectors are out of scope for v1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graphs are the hard part.&lt;/strong&gt; btop's look comes from braille sub-cell plotting
with a color gradient applied along the value axis, not from a stock chart
widget. Expect…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/lovestaco/ratatop" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Today's takeaway is small but general: when you draw two things next to each other, you have implied they are comparable. &lt;/p&gt;

&lt;p&gt;If they are on different scales, you have drawn a lie. Share the axis or separate the charts.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>programming</category>
      <category>productivity</category>
      <category>cli</category>
      <category>rust</category>
    </item>
    <item>
      <title>ratatop day 3: disks, statvfs, and my first unsafe block</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:17:57 +0000</pubDate>
      <link>https://dev.to/lovestaco/ratatop-day-3-disks-statvfs-and-my-first-unsafe-block-496e</link>
      <guid>https://dev.to/lovestaco/ratatop-day-3-disks-statvfs-and-my-first-unsafe-block-496e</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc?utm_source=ratatop" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Day three of building a btop-style system monitor in Rust with &lt;a href="https://ratatui.rs" rel="noopener noreferrer"&gt;ratatui&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Day one was the CPU box. &lt;/p&gt;

&lt;p&gt;Day two was memory, where I learned that &lt;code&gt;MemFree&lt;/code&gt; is a number nobody should look at.&lt;/p&gt;

&lt;p&gt;Today: disks.&lt;/p&gt;

&lt;p&gt;And this one broke a rule I had set for myself on day one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbndf7afjdeu4smd3f8mn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbndf7afjdeu4smd3f8mn.png" alt=" " width="800" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  There is no /proc file for this
&lt;/h2&gt;

&lt;p&gt;My rule was simple. &lt;/p&gt;

&lt;p&gt;Read &lt;code&gt;/proc&lt;/code&gt; and &lt;code&gt;/sys&lt;/code&gt;, never shell out to another tool.&lt;/p&gt;

&lt;p&gt;CPU came from &lt;code&gt;/proc/stat&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Memory came from &lt;code&gt;/proc/meminfo&lt;/code&gt;. Disks would come from... something.&lt;/p&gt;

&lt;p&gt;I went looking for the file. &lt;code&gt;/proc/mounts&lt;/code&gt; tells you what is mounted. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;/proc/diskstats&lt;/code&gt; tells you how much I/O happened. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;/sys/block/*&lt;/code&gt; tells you device sizes.&lt;/p&gt;

&lt;p&gt;None of them tell you how full a filesystem is.&lt;/p&gt;

&lt;p&gt;That number does not exist as a file anywhere. It only exists behind a syscall: &lt;code&gt;statvfs(2)&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The kernel computes it on demand from the filesystem's own metadata, and the only way to ask is to actually make the call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2msuz6ii9vfnshfv0re2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2msuz6ii9vfnshfv0re2.png" alt=" " width="360" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I had two options.&lt;/p&gt;

&lt;p&gt;Shell out to &lt;code&gt;df&lt;/code&gt; and parse it, which breaks my rule and drags in output-format fragility across distros.&lt;/p&gt;

&lt;p&gt;Or make the syscall myself, which means the &lt;code&gt;libc&lt;/code&gt; crate, which means &lt;code&gt;unsafe&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I picked the syscall. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;df&lt;/code&gt; itself is just a &lt;code&gt;statvfs&lt;/code&gt; wrapper with a printf on top. &lt;/p&gt;

&lt;p&gt;Spawning a process to read a number the kernel would hand me directly is silly.&lt;/p&gt;

&lt;h2&gt;
  
  
  My first unsafe block
&lt;/h2&gt;

&lt;p&gt;Rust's standard library has no &lt;code&gt;statvfs&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;So this is FFI territory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CString&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// SAFETY: `path` is a valid NUL-terminated string that outlives the call,&lt;/span&gt;
    &lt;span class="c1"&gt;// and `vfs` is fully initialised by statvfs before we read it. A failed call&lt;/span&gt;
    &lt;span class="c1"&gt;// is reported through the return value, which we check.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;vfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;vfs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;statvfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;zeroed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;statvfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;vfs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;vfs&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;frsize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vfs&lt;/span&gt;&lt;span class="py"&gt;.f_frsize&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vfs&lt;/span&gt;&lt;span class="py"&gt;.f_blocks&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.saturating_mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frsize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;free&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vfs&lt;/span&gt;&lt;span class="py"&gt;.f_bavail&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.saturating_mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frsize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="nf"&gt;.min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Three things worth pointing out, because they are the parts I had to actually think about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;CString::new&lt;/code&gt; is not optional.&lt;/strong&gt; C wants a NUL-terminated string. Rust &lt;code&gt;&amp;amp;str&lt;/code&gt; is not NUL-terminated, it carries a length. If you hand C a pointer to raw Rust string bytes, it reads until it happens to find a zero byte, which could be well past the end of your string. &lt;code&gt;CString&lt;/code&gt; does the conversion properly, and it returns a &lt;code&gt;Result&lt;/code&gt; because a Rust string containing an interior NUL cannot be represented at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;mem::zeroed()&lt;/code&gt; looks worse than it is.&lt;/strong&gt; We are handing C a buffer to fill in. &lt;code&gt;statvfs&lt;/code&gt; writes every field before returning success. Zeroing first means that even in some bizarre partial-write scenario we get zeros rather than whatever was on the stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;SAFETY&lt;/code&gt; comment is not decoration.&lt;/strong&gt; Rust convention is that every &lt;code&gt;unsafe&lt;/code&gt; block gets a comment explaining why it is actually safe. Writing that comment is a genuinely useful exercise. It forces you to enumerate your assumptions, and if you cannot write the comment, that is your answer about whether the code is correct.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4jf7ebv1bxmzldj42qw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4jf7ebv1bxmzldj42qw.png" alt=" " width="360" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The nice part is that the unsafety is contained.&lt;/p&gt;

&lt;p&gt;One function, ten lines, and a safe &lt;code&gt;Option&amp;lt;(u64, u64)&amp;gt;&lt;/code&gt; comes out the other end. Everything upstream of it is ordinary safe Rust that cannot tell the difference.&lt;/p&gt;
&lt;h2&gt;
  
  
  Your filesystem is lying to you by exactly 5%
&lt;/h2&gt;

&lt;p&gt;Now the fun part.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;statvfs&lt;/code&gt; gives you three block counts, and picking the wrong one gives you the wrong answer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;f_blocks&lt;/code&gt;: total blocks in the filesystem&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f_bfree&lt;/code&gt;: blocks that are genuinely unused&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f_bavail&lt;/code&gt;: blocks available &lt;strong&gt;to an unprivileged process&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those last two are not the same, and the gap is bigger than you would guess. Real numbers from my root filesystem:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total   91.1 GiB
bfree   23.7 GiB
bavail  19.0 GiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is 4.67 GiB, exactly 5.1% of the disk, that is free but that I am not allowed to use.&lt;/p&gt;

&lt;p&gt;ext4 reserves 5% of the filesystem for root by default. The idea is that if a runaway process fills the disk, root still has room to log in and clean up, and the filesystem has room to avoid the fragmentation that comes with running completely full.&lt;/p&gt;

&lt;p&gt;Which number you subtract changes the headline figure:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;used via f_bavail   79.1%
used via f_bfree    74.0%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Five percentage points, from one field name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdd1vv27yr6owyz4jqgqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdd1vv27yr6owyz4jqgqw.png" alt=" " width="792" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;btop uses &lt;code&gt;f_bavail&lt;/code&gt;, and so does &lt;code&gt;df&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;That is the honest choice for a monitor: report the space you can actually use, and treat root's reserve as spoken for. &lt;/p&gt;

&lt;p&gt;Showing 74% when &lt;code&gt;df&lt;/code&gt; says 79% would just make the tool look broken.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcysr9lbwdijvbz9m43kd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcysr9lbwdijvbz9m43kd.png" alt=" " width="360" height="263"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Which mounts are even real?
&lt;/h2&gt;

&lt;p&gt;Next problem. &lt;code&gt;/proc/self/mounts&lt;/code&gt; on my laptop has around 40 entries. &lt;/p&gt;

&lt;p&gt;Four of them are disks.&lt;/p&gt;

&lt;p&gt;The rest is &lt;code&gt;proc&lt;/code&gt;, &lt;code&gt;sysfs&lt;/code&gt;, &lt;code&gt;cgroup2&lt;/code&gt;, &lt;code&gt;tmpfs&lt;/code&gt;, a dozen snap squashfs images, and a couple of FUSE mounts from the desktop session.&lt;/p&gt;

&lt;p&gt;The obvious move is a blocklist of filesystem types to skip. &lt;/p&gt;

&lt;p&gt;I started writing one, then realised the kernel already publishes exactly this information:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat /proc/filesystems
nodev   sysfs
nodev   tmpfs
nodev   proc
nodev   cgroup2
    ext4
    vfat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Anything flagged &lt;code&gt;nodev&lt;/code&gt; is not backed by a block device. &lt;/p&gt;

&lt;p&gt;That is the kernel telling you, authoritatively, which filesystem types cannot possibly be a disk.&lt;/p&gt;

&lt;p&gt;So instead of maintaining a list that goes stale, I read theirs:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;
    &lt;span class="nf"&gt;.lines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.filter_map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="nf"&gt;.strip_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nodev"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="nf"&gt;.trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;New pseudo-filesystem lands in a future kernel? Excluded automatically, no code change.&lt;/p&gt;

&lt;p&gt;There is a second filter that does a lot of work for one line: the mount's device has to start with &lt;code&gt;/&lt;/code&gt;. A real partition is &lt;code&gt;/dev/nvme0n1p2&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Pseudo-filesystems put a bare name there instead, like &lt;code&gt;proc&lt;/code&gt; or &lt;code&gt;gvfsd-fuse&lt;/code&gt;. No slash, not a disk.&lt;/p&gt;
&lt;h2&gt;
  
  
  The kernel escapes your mount paths
&lt;/h2&gt;

&lt;p&gt;Small one, but it would have been a confusing bug.&lt;/p&gt;

&lt;p&gt;Mount a disk at a path with a space in it, and &lt;code&gt;/proc/self/mounts&lt;/code&gt; does not give you the space:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dev/sdb1 /mnt/my\040disk ext4 rw,relatime 0 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;\040&lt;/code&gt; is octal for a space. The kernel escapes spaces, tabs, newlines and backslashes this way, because the file is whitespace-separated and an unescaped space would make the line unparseable.&lt;/p&gt;

&lt;p&gt;If you pass that raw string to &lt;code&gt;statvfs&lt;/code&gt;, it fails. No such path exists. The disk silently vanishes from your monitor and you have no idea why.&lt;/p&gt;

&lt;p&gt;So you decode it before using it. Four characters starting with a backslash, parse the next three as octal, emit the byte.&lt;/p&gt;
&lt;h2&gt;
  
  
  I/O comes from somewhere else entirely
&lt;/h2&gt;

&lt;p&gt;Usage is a syscall. Throughput is back to reading files.&lt;/p&gt;

&lt;p&gt;btop reads &lt;code&gt;/sys/block/&amp;lt;device&amp;gt;/stat&lt;/code&gt;, which gives you a device's cumulative counters:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;234345 84965 16346864 796898 486240 189824 21513416 23255863 0 2406558 24197776
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The fields I care about are index 2 (sectors read), index 6 (sectors written), and index 9 (&lt;code&gt;io_ticks&lt;/code&gt;, milliseconds the device spent with a request in flight).&lt;/p&gt;

&lt;p&gt;Two traps here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A sector is always 512 bytes in this file.&lt;/strong&gt; Not the device's real block size. My NVMe drive uses 4096-byte blocks, but these counters are still expressed in 512-byte units, for backwards compatibility with software written when that was universal. Multiply by 4096 and your throughput numbers come out eight times too high.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Partitions are nested.&lt;/strong&gt; &lt;code&gt;/sys/block/nvme0n1/stat&lt;/code&gt; exists for the whole drive. The partition I actually mounted lives at &lt;code&gt;/sys/block/nvme0n1/nvme0n1p2/stat&lt;/code&gt;, one level deeper. So the lookup checks the top level first, then scans one level down.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;io_ticks&lt;/code&gt; is the nice one. It is busy-milliseconds, so dividing by the elapsed wall-clock milliseconds gives you utilisation directly:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;busy_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counters&lt;/span&gt;&lt;span class="py"&gt;.io_ticks&lt;/span&gt;&lt;span class="nf"&gt;.saturating_sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="py"&gt;.io_ticks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="py"&gt;.io_util&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;busy_ms&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1000.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Same delta pattern as the CPU box on day one. Third time I have written &lt;code&gt;saturating_sub&lt;/code&gt; on a monotonic counter in this project, and it has earned its keep every time: devices get unplugged and re-added, and counters reset to zero when they do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhrkx9fylt6lpdvj7ats7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhrkx9fylt6lpdvj7ats7.png" alt=" " width="800" height="748"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Swap is a disk that isn't one
&lt;/h2&gt;

&lt;p&gt;btop lists swap in the disks box, which surprised me until it made sense. You care about how full it is, in the same way and for the same reasons.&lt;/p&gt;

&lt;p&gt;But it has no filesystem, so &lt;code&gt;statvfs&lt;/code&gt; is meaningless. And it may be a file, a partition, or several of both, so there is no single block device to read I/O from.&lt;/p&gt;

&lt;p&gt;It comes from &lt;code&gt;/proc/meminfo&lt;/code&gt; instead:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SwapTotal:      21092348 kB
SwapFree:       19857688 kB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So swap is a row that shares the layout but skips two of the three data sources. &lt;/p&gt;

&lt;p&gt;In my code it is a &lt;code&gt;Disk&lt;/code&gt; with &lt;code&gt;is_swap: true&lt;/code&gt;, and the render function checks that flag before drawing the I/O graph. &lt;/p&gt;

&lt;p&gt;Drawing an empty graph there would imply a measurement of zero, when the truth is there is no measurement at all.&lt;/p&gt;

&lt;p&gt;The distinction between "zero" and "unknown" is one of those things that seems pedantic until your tool confidently reports the wrong thing.&lt;/p&gt;
&lt;h2&gt;
  
  
  The ratatui side was the easy part
&lt;/h2&gt;

&lt;p&gt;Genuinely almost nothing to say here, which is the point.&lt;/p&gt;

&lt;p&gt;Three rows per disk, laid out with the same &lt;code&gt;Layout&lt;/code&gt; calls as the other boxes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name_row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;io_row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;used_row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nn"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nn"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nn"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;.areas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;Meter&lt;/code&gt; widget from day one and the &lt;code&gt;BrailleGraph&lt;/code&gt; from day one both dropped straight in with zero changes. Third box, same two widgets. That is the payoff for writing them as plain structs with one &lt;code&gt;render&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;One layout decision worth mentioning: the box only draws disks that completely fit.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="py"&gt;.height&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;ROWS_PER_DISK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Integer division, so a disk with room for only two of its three rows is not drawn at all. A half-rendered disk looks like a bug, whereas a missing one just looks like a small window.&lt;/p&gt;
&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Network. &lt;code&gt;/proc/net/dev&lt;/code&gt; is another counter file, so the delta machinery is already there, but auto-scaling units across five orders of magnitude will be new.&lt;/p&gt;

&lt;p&gt;Then processes, which is the big one. &lt;/p&gt;

&lt;p&gt;Reading &lt;code&gt;/proc/[pid]/stat&lt;/code&gt; for every process, every tick, and sorting it.&lt;/p&gt;

&lt;p&gt;Code so far:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/lovestaco" rel="noopener noreferrer"&gt;
        lovestaco
      &lt;/a&gt; / &lt;a href="https://github.com/lovestaco/ratatop" rel="noopener noreferrer"&gt;
        ratatop
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Keep an eye on what's cooking in your system 
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/lovestaco/ratatop/media/cover.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flovestaco%2Fratatop%2FHEAD%2Fmedia%2Fcover.png" alt="ratatop" width="100%"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;A reimplementation of &lt;a href="https://github.com/aristocratos/btop" rel="noopener noreferrer"&gt;btop&lt;/a&gt; in Rust, built on
&lt;a href="https://ratatui.rs" rel="nofollow noopener noreferrer"&gt;ratatui&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The goal is a resource monitor that feels like btop: the four-box layout, braille
graphs with gradient fills, mouse-driven navigation, and a themable process
manager, but written as a modern Rust TUI.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Status&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The CPU box works.&lt;/strong&gt; It samples real data and renders btop's layout:&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/lovestaco/ratatop/media/ratatop.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flovestaco%2Fratatop%2FHEAD%2Fmedia%2Fratatop.png" alt="ratatop running"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Per-core braille history graphs, a gradient load bar with sub-cell resolution
frequency, package temperature, battery, and load averages.&lt;/p&gt;
&lt;p&gt;Everything else, the memory, network, and process boxes, is still to come.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Design notes&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data source:&lt;/strong&gt; &lt;code&gt;/proc&lt;/code&gt; and &lt;code&gt;/sys&lt;/code&gt; on Linux first, read directly rather than
shelling out. btop's &lt;code&gt;src/linux/btop_collect.cpp&lt;/code&gt; is the reference for what to
read and how often; the BSD/macOS collectors are out of scope for v1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graphs are the hard part.&lt;/strong&gt; btop's look comes from braille sub-cell plotting
with a color gradient applied along the value axis, not from a stock chart
widget. Expect…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/lovestaco/ratatop" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Today's lesson, if there is one: the rule I set myself ("only read files, never shell out") was a good rule that had exactly one legitimate exception, and finding out &lt;em&gt;why&lt;/em&gt; it was an exception taught me more than following it would have.&lt;/p&gt;

&lt;p&gt;Sometimes the file you are looking for does not exist because the number was never a file.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>cli</category>
      <category>beginners</category>
      <category>programming</category>
      <category>rust</category>
    </item>
    <item>
      <title>ratatop day 2: the memory box, and the lie in `free -h`</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Wed, 29 Jul 2026 22:08:04 +0000</pubDate>
      <link>https://dev.to/lovestaco/ratatop-day-2-the-memory-box-and-the-lie-in-free-h-h67</link>
      <guid>https://dev.to/lovestaco/ratatop-day-2-the-memory-box-and-the-lie-in-free-h-h67</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc?utm_source=ratatop" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.to/lovestaco/building-ratatop-a-rust-system-monitor-with-ratatui-50n"&gt;Yesterday&lt;/a&gt; I got the CPU box working in &lt;a href="https://ratatui.rs" rel="noopener noreferrer"&gt;ratatui&lt;/a&gt; and wrote up my first impressions coming from Bubble Tea.&lt;/p&gt;

&lt;p&gt;Today: memory.&lt;/p&gt;

&lt;p&gt;I assumed this would be the easy one. &lt;/p&gt;

&lt;p&gt;CPU needed delta tracking between samples, per-core bookkeeping, braille bit twiddling. Memory is just a file with numbers in it. &lt;/p&gt;

&lt;p&gt;Read it, print it, go home.&lt;/p&gt;

&lt;p&gt;Reader, it was not the easy one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F04o2ivxvg06zu1t6uqjh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F04o2ivxvg06zu1t6uqjh.png" alt=" " width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Free memory is not free memory
&lt;/h2&gt;

&lt;p&gt;Look at that box again. Total 13.5 GiB. Free: 271 MiB.&lt;/p&gt;

&lt;p&gt;Two percent free. That sounds like a machine about to fall over.&lt;/p&gt;

&lt;p&gt;It is completely fine. I have 7.10 GiB available right now.&lt;/p&gt;

&lt;p&gt;This is the thing that trips up everyone reading &lt;code&gt;/proc/meminfo&lt;/code&gt; for the first time, and it tripped me up too. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;MemFree&lt;/code&gt; means memory the kernel is doing absolutely nothing with. Not caching a file, not buffering a write, nothing. Just sitting there.&lt;/p&gt;

&lt;p&gt;And the kernel hates that. Unused RAM is wasted RAM. So it fills spare memory with page cache, and hands it back the instant a process actually needs it.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;MemFree&lt;/code&gt; on a healthy machine trends toward zero, by design. It is a measure of how much RAM your kernel is failing to put to work.&lt;/p&gt;

&lt;p&gt;The number you actually want is &lt;code&gt;MemAvailable&lt;/code&gt;, added to the kernel in 3.14 precisely because everyone was misreading &lt;code&gt;MemFree&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;It is an estimate of how much you can allocate right now without swapping, and it accounts for the fact that most of that cache is reclaimable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdmrmarzfhs1fo2n0thv3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdmrmarzfhs1fo2n0thv3.png" alt=" " width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;Used&lt;/code&gt; and &lt;code&gt;Available&lt;/code&gt; add up to &lt;code&gt;Total&lt;/code&gt;, but &lt;code&gt;Cached&lt;/code&gt; and &lt;code&gt;Free&lt;/code&gt; do not slot in neatly beside them. &lt;/p&gt;

&lt;p&gt;Cached memory is counted inside available. They overlap. &lt;/p&gt;

&lt;p&gt;Trying to make all four sum to total is a trap, and I definitely tried for a bit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Meme 1&lt;/strong&gt;&lt;br&gt;
Template: &lt;strong&gt;Bernie Sanders "I am once again asking"&lt;/strong&gt;&lt;br&gt;
Text: "I am once again asking you to stop reading MemFree"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  So how do you compute "used"?
&lt;/h2&gt;

&lt;p&gt;Here is where I went looking at how &lt;a href="https://github.com/aristocratos/btop" rel="noopener noreferrer"&gt;btop&lt;/a&gt; does it instead of inventing my own.&lt;/p&gt;

&lt;p&gt;The obvious approach is to add up the parts. &lt;/p&gt;

&lt;p&gt;Total minus free minus cached minus buffers, something like that. &lt;/p&gt;

&lt;p&gt;This is what a lot of tools did historically, and it is where the old "Linux ate my RAM" confusion comes from.&lt;/p&gt;

&lt;p&gt;btop does the simple thing instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;used = MemTotal - MemAvailable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is it. Let the kernel do the hard estimate, because it is the only thing that actually knows what is reclaimable.&lt;/p&gt;

&lt;p&gt;In Rust, my collector boils down to this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Plain &lt;code&gt;u64&lt;/code&gt; bytes, no units baked in. Formatting is the UI's problem, not the collector's.&lt;/p&gt;
&lt;h2&gt;
  
  
  The bug that a substring gave me for free
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/proc/meminfo&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;MemTotal&lt;/span&gt;:       &lt;span class="m"&gt;14177796&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;
&lt;span class="n"&gt;MemFree&lt;/span&gt;:          &lt;span class="m"&gt;497404&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;
&lt;span class="n"&gt;MemAvailable&lt;/span&gt;:    &lt;span class="m"&gt;7490900&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;
&lt;span class="n"&gt;Buffers&lt;/span&gt;:         &lt;span class="m"&gt;1235656&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;
&lt;span class="n"&gt;Cached&lt;/span&gt;:          &lt;span class="m"&gt;5146132&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;
&lt;span class="n"&gt;SwapCached&lt;/span&gt;:        &lt;span class="m"&gt;81068&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Spot the landmine.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SwapCached&lt;/code&gt; contains the substring &lt;code&gt;Cached&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;If you parse this with a &lt;code&gt;contains&lt;/code&gt; or a loose prefix match, &lt;code&gt;SwapCached&lt;/code&gt; arrives four lines later and quietly overwrites your real cached value with something roughly 60 times smaller.&lt;/p&gt;

&lt;p&gt;Your box renders. No error. No panic. Just a wrong number sitting there looking plausible.&lt;/p&gt;

&lt;p&gt;The fix is boring: split on the colon, match the label exactly.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="nf"&gt;.split_once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"MemTotal"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="py"&gt;.total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"MemFree"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="py"&gt;.free&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"MemAvailable"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s"&gt;"Cached"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="py"&gt;.cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I wrote a test named &lt;code&gt;swap_cached_does_not_get_mistaken_for_cached&lt;/code&gt; purely so the next person who "simplifies" this parser gets caught.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fitczlo7ouuidfvkylqhg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fitczlo7ouuidfvkylqhg.png" alt=" " width="360" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also worth knowing: the &lt;code&gt;kB&lt;/code&gt; suffix in that file is a lie too. Those are KiB, 1024 bytes, not 1000. So the multiply is &lt;code&gt;&amp;lt;&amp;lt; 10&lt;/code&gt;, not &lt;code&gt;* 1000&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three significant figures, and why
&lt;/h2&gt;

&lt;p&gt;Look at the value column again:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;13.5 GiB
6.42 GiB
 271 MiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Different decimal counts. That is deliberate, and it is what btop does.&lt;/p&gt;

&lt;p&gt;The rule is three significant figures, always. Under 10, you get two decimals. Between 10 and 100, one. Above 100, none. The number is always exactly three digits wide, so the column stays aligned no matter what the values do.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;decimals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Small thing. But a column that jitters between &lt;code&gt;6.4 GiB&lt;/code&gt; and &lt;code&gt;13.52 GiB&lt;/code&gt; looks amateurish in a way you feel before you can name it.&lt;/p&gt;

&lt;p&gt;One thing I got wrong on the first pass: my loop divided by 1024 while the value was too large, with no guard on the unit table. &lt;/p&gt;

&lt;p&gt;Feed that a &lt;code&gt;u64::MAX&lt;/code&gt; and it walks straight off the end of the array. &lt;/p&gt;

&lt;p&gt;Now it stops at TiB, with a test that passes &lt;code&gt;u64::MAX&lt;/code&gt; specifically.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where ratatui made this pleasant
&lt;/h2&gt;

&lt;p&gt;Two things stood out building a second box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Each field gets its own gradient.&lt;/strong&gt; btop's theme gives used, available, cached and free their own three-stop colour ramps, and I lifted the exact hex values out of &lt;code&gt;btop_theme.cpp&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Because a &lt;code&gt;Gradient&lt;/code&gt; is just a value in my code, colouring a meter is passing a different one in:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gradient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Used:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="py"&gt;.used&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="nn"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MEM_USED&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Available:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="py"&gt;.available&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MEM_AVAILABLE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cached:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="py"&gt;.cached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="nn"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MEM_CACHED&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Free:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="py"&gt;.free&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="nn"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MEM_FREE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;render_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;render_meter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="nf"&gt;.percent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;gradient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Immediate mode really pays off here. &lt;/p&gt;

&lt;p&gt;There is no widget to construct, register, style and later mutate. &lt;/p&gt;

&lt;p&gt;You are in a loop, you have a &lt;code&gt;Rect&lt;/code&gt;, you draw. Four fields, four iterations, done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The meter widget was already written.&lt;/strong&gt; I built it for the CPU bar on day one, and reusing it for memory took zero changes. &lt;/p&gt;

&lt;p&gt;That is the nice consequence of ratatui's &lt;code&gt;Widget&lt;/code&gt; trait being tiny: a widget is a struct plus one &lt;code&gt;render&lt;/code&gt; method, so a generic one you wrote yesterday just works today.&lt;/p&gt;

&lt;p&gt;I did briefly add a &lt;code&gt;Meter::braille()&lt;/code&gt; method to make the memory bars look different. Then I noticed it returned &lt;code&gt;self&lt;/code&gt; completely unchanged, doing nothing but making the call site read fancier. Deleted it. A method that lies about doing something is worse than no method.&lt;/p&gt;
&lt;h2&gt;
  
  
  Restructuring, because two boxes is a crowd
&lt;/h2&gt;

&lt;p&gt;Day one had everything in one &lt;code&gt;ui.rs&lt;/code&gt;. That was fine for one box and immediately wrong for two.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD
    M["ui/mod.rs&amp;lt;br/&amp;gt;splits the terminal, paints background"]
    M --&amp;gt; C["ui/cpu.rs&amp;lt;br/&amp;gt;bar, cores, load"]
    M --&amp;gt; Mem["ui/mem.rs&amp;lt;br/&amp;gt;values, meters, formatting"]
    C --&amp;gt; W["widgets/&amp;lt;br/&amp;gt;BrailleGraph, Meter"]
    Mem --&amp;gt; W
    C --&amp;gt; Col["collect/cpu.rs"]
    Mem --&amp;gt; Col2["collect/mem.rs"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The useful change was moving layout decisions &lt;em&gt;up&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Previously &lt;code&gt;main.rs&lt;/code&gt; computed the box rectangle itself, which was fine when there was exactly one box and completely wrong the moment memory needed the leftover height. &lt;/p&gt;

&lt;p&gt;Now &lt;code&gt;main.rs&lt;/code&gt; hands over the entire terminal and &lt;code&gt;ui::render&lt;/code&gt; decides who gets what:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cpu_area&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nn"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpu_height&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nn"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;.areas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;CPU takes the top, memory takes a fixed width below it, and the rest stays empty for the network and process boxes. &lt;/p&gt;

&lt;p&gt;Adding a box is now a matter of carving another &lt;code&gt;Rect&lt;/code&gt; out of &lt;code&gt;rest&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Network, probably. &lt;code&gt;/proc/net/dev&lt;/code&gt; is another counter file, so it needs the same delta treatment the CPU box uses, plus auto-scaling units because network speeds swing across five orders of magnitude.&lt;/p&gt;

&lt;p&gt;Then processes, which is the big one. That is where &lt;code&gt;/proc/[pid]/stat&lt;/code&gt; lives, and where I suspect I will learn a great deal about how many edge cases a process table has.&lt;/p&gt;

&lt;p&gt;Code so far: &lt;a href="https://github.com/lovestaco/ratatop" rel="noopener noreferrer"&gt;lovestaco/ratatop&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you take one thing from this post, make it this: stop looking at &lt;code&gt;free&lt;/code&gt;. Look at &lt;code&gt;available&lt;/code&gt;. Your machine is fine.&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>programming</category>
      <category>rust</category>
      <category>cli</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building ratatop: a Rust System Monitor with ratatui</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Tue, 28 Jul 2026 12:24:46 +0000</pubDate>
      <link>https://dev.to/lovestaco/building-ratatop-a-rust-system-monitor-with-ratatui-50n</link>
      <guid>https://dev.to/lovestaco/building-ratatop-a-rust-system-monitor-with-ratatui-50n</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For a long while now I've been trying to learn how to build TUI apps.&lt;/p&gt;

&lt;p&gt;Last month I made peektea, a keyboard-driven terminal file browser. The whole journey is written up here: &lt;strong&gt;&lt;a href="https://dev.to/lovestaco/series/40422"&gt;the peektea series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/lovestaco" rel="noopener noreferrer"&gt;
        lovestaco
      &lt;/a&gt; / &lt;a href="https://github.com/lovestaco/peektea" rel="noopener noreferrer"&gt;
        peektea
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Peek First. Open Later. A TUI file browser built with bubble tea
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;A minimal terminal file browser built with &lt;a href="https://github.com/charmbracelet/bubbletea" rel="noopener noreferrer"&gt;Bubble Tea&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Peek through your filesystem with arrow keys (or vim keys), then pour each file straight into the app you've configured for it.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;A quick peek before you steep
Click below to watch the demo on YouTube (7m), or scroll down to see gif (10s)
&lt;a href="https://www.youtube.com/watch?v=yDNN5x9Y9Ok" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flovestaco%2Fpeektea%2FHEAD%2Fmedia%2Fdemo_thumb.png" alt="peektea demo"&gt;&lt;/a&gt;
demo in gif version
&lt;a rel="noopener noreferrer" href="https://github.com/lovestaco/peektea/media/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flovestaco%2Fpeektea%2FHEAD%2Fmedia%2Fdemo.gif" alt="demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;curl -fsSL https://raw.githubusercontent.com/lovestaco/peektea/master/scripts/install.sh &lt;span class="pl-k"&gt;|&lt;/span&gt; sh&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Download a binary&lt;/strong&gt; (no Go required) — grab the latest release for your platform from the &lt;a href="https://github.com/lovestaco/peektea/releases/latest" rel="noopener noreferrer"&gt;releases page&lt;/a&gt;:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linux x86-64&lt;/td&gt;
&lt;td&gt;&lt;code&gt;peektea_*_linux_amd64.tar.gz&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux arm64&lt;/td&gt;
&lt;td&gt;&lt;code&gt;peektea_*_linux_arm64.tar.gz&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS x86-64&lt;/td&gt;
&lt;td&gt;&lt;code&gt;peektea_*_darwin_amd64.tar.gz&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS Apple Silicon&lt;/td&gt;
&lt;td&gt;&lt;code&gt;peektea_*_darwin_arm64.tar.gz&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Extract and put the &lt;code&gt;peektea&lt;/code&gt; binary anywhere on your &lt;code&gt;$PATH&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Install with Go:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;go install github.com/lovestaco/peektea@latest&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Build from source:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/lovestaco/peektea
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; peektea
make install&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;make install&lt;/code&gt; puts the binary in &lt;code&gt;~/go/bin&lt;/code&gt; and figures out &lt;code&gt;$PATH&lt;/code&gt; for you:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Already reachable — done, nothing to do.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~/.local/bin&lt;/code&gt; is on…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/lovestaco/peektea" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;That one was &lt;a href="https://github.com/charmbracelet/bubbletea" rel="noopener noreferrer"&gt;Bubble Tea&lt;/a&gt; with Go.&lt;/p&gt;

&lt;p&gt;So I got curious about what the other side looks like. &lt;/p&gt;

&lt;p&gt;What can Rust do here?&lt;/p&gt;

&lt;p&gt;Which is how I landed on &lt;a href="https://ratatui.rs" rel="noopener noreferrer"&gt;ratatui&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I'm connected to &lt;a href="https://github.com/orhun" rel="noopener noreferrer"&gt;Orhun Parmaksız&lt;/a&gt; on LinkedIn, I keep seeing his posts about it on my feed, and eventually curiosity won.&lt;/p&gt;

&lt;p&gt;So here we are. &lt;/p&gt;

&lt;p&gt;I built a basic Linux visual monitor to learn the library:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbus80b3mt0zxs5klvs1r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbus80b3mt0zxs5klvs1r.png" alt="ratatop" width="798" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the next few days I'll be poking at this and writing down whatever I learn. This is post one.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what is ratatui?
&lt;/h2&gt;

&lt;p&gt;ratatui is a Rust library for building terminal user interfaces. &lt;/p&gt;

&lt;p&gt;It grew out of &lt;code&gt;tui-rs&lt;/code&gt;, which went unmaintained back in 2023, and a group of folks picked it up and ran with it. &lt;/p&gt;

&lt;p&gt;Orhun is one of the maintainers.&lt;/p&gt;

&lt;p&gt;The name is a &lt;a href="https://en.wikipedia.org/wiki/Ratatouille_(film)" rel="noopener noreferrer"&gt;Ratatouille&lt;/a&gt; joke, and honestly the film's whole "anyone can cook" thing turned out to be the right energy for a first project. &lt;/p&gt;

&lt;p&gt;You do not need to be a Rust wizard to get something on screen.&lt;/p&gt;

&lt;p&gt;One thing to get straight early: ratatui is a &lt;strong&gt;library&lt;/strong&gt;, not a framework.&lt;/p&gt;

&lt;p&gt;Bubble Tea hands you an architecture. &lt;/p&gt;

&lt;p&gt;You fill in &lt;code&gt;Model&lt;/code&gt;, &lt;code&gt;Update&lt;/code&gt;, &lt;code&gt;View&lt;/code&gt;, and it runs the show. &lt;/p&gt;

&lt;p&gt;ratatui hands you a drawing surface and says good luck.&lt;/p&gt;

&lt;p&gt;That difference is the whole post, really.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing that broke my brain first
&lt;/h2&gt;

&lt;p&gt;Bubble Tea is built on the &lt;a href="https://guide.elm-lang.org/architecture/" rel="noopener noreferrer"&gt;Elm Architecture&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;State goes in, messages come back, you return a new model, the framework re-renders. &lt;/p&gt;

&lt;p&gt;It is retained and it is opinionated.&lt;/p&gt;

&lt;p&gt;ratatui is &lt;strong&gt;immediate mode&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;There is no widget tree living in memory between frames. &lt;/p&gt;

&lt;p&gt;There is no &lt;code&gt;View()&lt;/code&gt; that the framework calls for you. &lt;/p&gt;

&lt;p&gt;Every single frame, you build the widgets from scratch, draw them, and throw them away.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxlgi91c3r51bby8fshb0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxlgi91c3r51bby8fshb0.png" alt=" " width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Coming from Bubble Tea this felt wrong for about a day. &lt;/p&gt;

&lt;p&gt;Where does the state live? Wherever you want. Who calls the render? You do. When do widgets get cleaned up? They were never alive.&lt;/p&gt;

&lt;p&gt;Then it clicked, and it felt great. &lt;/p&gt;

&lt;p&gt;No lifecycle, no diffing your own state, no "why did this component not re-render". &lt;/p&gt;

&lt;p&gt;You want something on screen, you draw it.&lt;/p&gt;

&lt;p&gt;Every frame.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuep0lwr4f4ck69lczv73.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuep0lwr4f4ck69lczv73.png" alt=" " width="360" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The catch is that "you own the loop" also means you own everything else. &lt;br&gt;
Quitting, resizing, timing, keyboard input, all of it is yours now.&lt;/p&gt;
&lt;h2&gt;
  
  
  The entire app is one loop
&lt;/h2&gt;

&lt;p&gt;Here is genuinely the whole shape of a ratatui program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;terminal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;ratatui&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// alt screen + raw mode&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nn"&gt;ratatui&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                   &lt;span class="c1"&gt;// put the terminal back&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;DefaultTerminal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;terminal&lt;/span&gt;&lt;span class="nf"&gt;.draw&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello from the kitchen"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="nf"&gt;.render_widget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="nf"&gt;.area&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nn"&gt;event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_millis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nf"&gt;.as_key_press_event&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="py"&gt;.code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nn"&gt;KeyCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(());&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;ratatui::init()&lt;/code&gt; is the modern convenience wrapper. &lt;/p&gt;

&lt;p&gt;It flips on raw mode and switches to the alternate screen buffer, the same trick &lt;code&gt;vim&lt;/code&gt; and &lt;code&gt;htop&lt;/code&gt; use so your shell history stays clean when you quit.&lt;/p&gt;

&lt;p&gt;In peektea that was &lt;code&gt;tea.WithAltScreen()&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Same idea, different kitchen.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ratatui::restore()&lt;/code&gt; puts everything back. &lt;/p&gt;

&lt;p&gt;Worth wiring this into a panic hook too, because a Rust panic inside raw mode leaves your terminal in a genuinely cursed state.&lt;/p&gt;

&lt;p&gt;Input is not ratatui's job at all. &lt;/p&gt;

&lt;p&gt;That comes from &lt;a href="https://github.com/crossterm-rs/crossterm" rel="noopener noreferrer"&gt;crossterm&lt;/a&gt;, which ratatui re-exports as &lt;code&gt;ratatui::crossterm&lt;/code&gt; so your versions cannot drift apart. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;event::poll&lt;/code&gt; with a timeout is what keeps the loop from blocking, so you can redraw on a timer instead of only on keypress.&lt;/p&gt;
&lt;h2&gt;
  
  
  Everything is a Rect
&lt;/h2&gt;

&lt;p&gt;The core layout idea is small: you have a &lt;code&gt;Rect&lt;/code&gt;, and you cut it into smaller &lt;code&gt;Rect&lt;/code&gt;s.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nn"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// exactly one row&lt;/span&gt;
    &lt;span class="nn"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;      &lt;span class="c1"&gt;// whatever is left over&lt;/span&gt;
    &lt;span class="nn"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// exactly one row&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;.areas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That &lt;code&gt;.areas()&lt;/code&gt; returning a fixed-size array is lovely. &lt;/p&gt;

&lt;p&gt;The compiler checks that you destructured exactly three areas from three constraints. &lt;/p&gt;

&lt;p&gt;Get it wrong and it does not build.&lt;/p&gt;

&lt;p&gt;The constraint types cover most of what you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Length(n)&lt;/code&gt; for exact cells&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Min(n)&lt;/code&gt; and &lt;code&gt;Max(n)&lt;/code&gt; for flexible with a bound&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Percentage(n)&lt;/code&gt; and &lt;code&gt;Ratio(a, b)&lt;/code&gt; for proportional&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Fill(weight)&lt;/code&gt; for splitting leftovers by weight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyn1tqhdj9lspaft6fygw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyn1tqhdj9lspaft6fygw.png" alt=" " width="799" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also &lt;code&gt;.spacing(n)&lt;/code&gt; to put gaps between chunks, and &lt;code&gt;.flex(Flex::Center)&lt;/code&gt; to control where slack goes when your constraints do not fill the space. &lt;/p&gt;

&lt;p&gt;I used &lt;code&gt;Flex::Center&lt;/code&gt; to centre a box, then swapped it to &lt;code&gt;Flex::Start&lt;/code&gt; to pin it to the top of the terminal. &lt;/p&gt;

&lt;p&gt;One enum, done.&lt;/p&gt;

&lt;p&gt;Nesting is just calling &lt;code&gt;Layout&lt;/code&gt; again on one of the pieces you got back. &lt;/p&gt;

&lt;p&gt;That is the whole layout system.&lt;/p&gt;
&lt;h2&gt;
  
  
  Widgets are cheap, disposable, and consumed
&lt;/h2&gt;

&lt;p&gt;A widget in ratatui is a plain value that gets eaten when you draw it. &lt;/p&gt;

&lt;p&gt;The trait is literally:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note &lt;code&gt;self&lt;/code&gt;, not &lt;code&gt;&amp;amp;self&lt;/code&gt;. It is consumed. &lt;/p&gt;

&lt;p&gt;You are not holding onto it.&lt;/p&gt;

&lt;p&gt;Stock widgets get you surprisingly far. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Block&lt;/code&gt; is the bordered container, and it composes nicely:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.borders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Borders&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.border_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;BorderType&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Rounded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// ╭─╮ instead of ┌─┐&lt;/span&gt;
    &lt;span class="nf"&gt;.title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CPU"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.title_bottom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"q to quit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.right_aligned&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="nf"&gt;.inner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// the area inside the borders&lt;/span&gt;
&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="nf"&gt;.render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;block.inner(area)&lt;/code&gt; is the one to remember. &lt;/p&gt;

&lt;p&gt;It gives you the space inside the border so your content does not get eaten by the frame.&lt;/p&gt;

&lt;p&gt;For text, the hierarchy is &lt;code&gt;Span&lt;/code&gt; (styled string) inside &lt;code&gt;Line&lt;/code&gt; (a row) inside &lt;code&gt;Text&lt;/code&gt; (many rows):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nn"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.fg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Red&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nn"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.fg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;White&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There is also a &lt;code&gt;Stylize&lt;/code&gt; trait that makes this much less shouty, giving you &lt;code&gt;"CPU".red().bold()&lt;/code&gt; directly on string literals. &lt;/p&gt;

&lt;p&gt;Very nice for quick work.&lt;/p&gt;
&lt;h2&gt;
  
  
  When the stock widgets are not enough
&lt;/h2&gt;

&lt;p&gt;This is where it got fun.&lt;/p&gt;

&lt;p&gt;The look I was going after uses &lt;a href="https://en.wikipedia.org/wiki/Braille_Patterns" rel="noopener noreferrer"&gt;braille&lt;/a&gt; characters to fit more resolution into a terminal cell. &lt;/p&gt;

&lt;p&gt;Each braille glyph is a 2x4 grid of dots, so one character cell holds 8 addressable points instead of 1.&lt;/p&gt;

&lt;p&gt;No stock widget does that. So you write one. You implement &lt;code&gt;Widget&lt;/code&gt;, you get a &lt;code&gt;Buffer&lt;/code&gt;, and you set cells directly.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;MyGraph&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
        &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="nf"&gt;.set_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'⣿'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="nf"&gt;.set_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.fg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Green&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;Buffer&lt;/code&gt; is a flat grid of &lt;code&gt;Cell&lt;/code&gt;s, where each cell holds a symbol plus its style. That is the entire drawing surface. &lt;/p&gt;

&lt;p&gt;Once you realise a custom widget is just "write characters into a grid", the intimidation factor drops off a cliff.&lt;/p&gt;

&lt;p&gt;Then I spent an embarrassing amount of time on the fact that braille dot numbering is not sequential. &lt;/p&gt;

&lt;p&gt;The bits go 1, 2, 3 down the left column, 4, 5, 6 down the right, and then 7 and 8 got bolted on later as a fourth row.&lt;/p&gt;

&lt;p&gt;So the bitmask is &lt;code&gt;0x01, 0x02, 0x04, 0x40&lt;/code&gt; on the left and &lt;code&gt;0x08, 0x10, 0x20, 0x80&lt;/code&gt; on the right. &lt;/p&gt;

&lt;p&gt;That &lt;code&gt;0x40&lt;/code&gt; sitting where &lt;code&gt;0x08&lt;/code&gt; should be is a 19th century design decision reaching out to ruin your afternoon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fze13d4fozspzu4hndi9p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fze13d4fozspzu4hndi9p.png" alt=" " width="360" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Worth it though. &lt;/p&gt;

&lt;p&gt;The payoff is graphs that look like graphs instead of blocks.&lt;/p&gt;
&lt;h2&gt;
  
  
  The one mistake worth confessing
&lt;/h2&gt;

&lt;p&gt;While drawing per-core graphs, I decided that any non-zero reading should light at least one dot.&lt;/p&gt;

&lt;p&gt;My reasoning: an idle core showing literally nothing looks broken.&lt;/p&gt;

&lt;p&gt;Reasonable. Also completely wrong.&lt;/p&gt;

&lt;p&gt;The aggregate CPU line is never zero. &lt;/p&gt;

&lt;p&gt;So every single column got exactly one dot, and my beautiful history graph rendered as a perfectly flat line, all the way across, forever.&lt;/p&gt;

&lt;p&gt;I had built a very expensive way to draw a dash.&lt;/p&gt;

&lt;p&gt;The fix was to delete my clever bit and let low values round down to blank. &lt;/p&gt;

&lt;p&gt;Those gaps are the entire texture. &lt;/p&gt;

&lt;p&gt;Letting values fall to nothing is what makes it look alive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq8jxhynj1jz8kac3c06m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq8jxhynj1jz8kac3c06m.png" alt=" " width="360" height="383"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How a frame actually reaches your terminal
&lt;/h2&gt;

&lt;p&gt;Last piece, because it explains a debugging trap I fell into.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fctibw95zjqv4xp4iq3lc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fctibw95zjqv4xp4iq3lc.png" alt=" " width="800" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ratatui keeps two buffers and only emits the cells that actually changed. &lt;/p&gt;

&lt;p&gt;That is why redrawing everything every frame is not wasteful.&lt;/p&gt;

&lt;p&gt;The trap: I tried to test a feature by piping the app's output and grepping for a string I expected on screen.&lt;/p&gt;

&lt;p&gt;Found nothing, assumed the feature was broken. It was not. &lt;/p&gt;

&lt;p&gt;Because of the diffing, a changed value never appears as a contiguous string in the output stream. It arrives as a few individual cells with cursor moves between them.&lt;/p&gt;

&lt;p&gt;Lesson: test your state and your widgets, not the escape codes.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I'd cook next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Loading theme files so colours are not hardcoded&lt;/li&gt;
&lt;li&gt;More collectors, because right now it only knows about the CPU&lt;/li&gt;
&lt;li&gt;Actually reading the ratatui &lt;a href="https://ratatui.rs/tutorials/" rel="noopener noreferrer"&gt;tutorials&lt;/a&gt; properly instead of learning by flailing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've been on the fence about TUIs, ratatui is a genuinely nice place to start. &lt;/p&gt;

&lt;p&gt;The API is small, the docs are good, and the immediate mode model means there is very little machinery hiding from you.&lt;/p&gt;

&lt;p&gt;Code is here if you want a look: &lt;strong&gt;lovestaco/ratatop&lt;/strong&gt; &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/lovestaco" rel="noopener noreferrer"&gt;
        lovestaco
      &lt;/a&gt; / &lt;a href="https://github.com/lovestaco/ratatop" rel="noopener noreferrer"&gt;
        ratatop
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Keep an eye on what's cooking in your system 
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/lovestaco/ratatop/media/cover.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flovestaco%2Fratatop%2FHEAD%2Fmedia%2Fcover.png" alt="ratatop" width="100%"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;A reimplementation of &lt;a href="https://github.com/aristocratos/btop" rel="noopener noreferrer"&gt;btop&lt;/a&gt; in Rust, built on
&lt;a href="https://ratatui.rs" rel="nofollow noopener noreferrer"&gt;ratatui&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The goal is a resource monitor that feels like btop: the four-box layout, braille
graphs with gradient fills, mouse-driven navigation, and a themable process
manager, but written as a modern Rust TUI.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Status&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The CPU box works.&lt;/strong&gt; It samples real data and renders btop's layout:&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/lovestaco/ratatop/media/ratatop.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flovestaco%2Fratatop%2FHEAD%2Fmedia%2Fratatop.png" alt="ratatop running"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Per-core braille history graphs, a gradient load bar with sub-cell resolution
frequency, package temperature, battery, and load averages.&lt;/p&gt;
&lt;p&gt;Everything else, the memory, network, and process boxes, is still to come.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Design notes&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data source:&lt;/strong&gt; &lt;code&gt;/proc&lt;/code&gt; and &lt;code&gt;/sys&lt;/code&gt; on Linux first, read directly rather than
shelling out. btop's &lt;code&gt;src/linux/btop_collect.cpp&lt;/code&gt; is the reference for what to
read and how often; the BSD/macOS collectors are out of scope for v1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graphs are the hard part.&lt;/strong&gt; btop's look comes from braille sub-cell plotting
with a color gradient applied along the value axis, not from a stock chart
widget. Expect…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/lovestaco/ratatop" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
 

&lt;p&gt;More soon. Anyone can cook.&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>programming</category>
      <category>cli</category>
      <category>beginners</category>
      <category>rust</category>
    </item>
    <item>
      <title>A VPN Is a Lie You Tell Your Kernel</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Mon, 27 Jul 2026 12:24:24 +0000</pubDate>
      <link>https://dev.to/lovestaco/a-vpn-is-a-lie-you-tell-your-kernel-41dj</link>
      <guid>https://dev.to/lovestaco/a-vpn-is-a-lie-you-tell-your-kernel-41dj</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A VPN is mostly a polite lie you tell your kernel. Here is how EdgeVPN tells it without a single central server, explained by actually reading the Go&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have used VPNs for years without really knowing what one &lt;em&gt;is&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;I typed &lt;code&gt;wg-quick up&lt;/code&gt;, saw a new interface show up in &lt;code&gt;ip a&lt;/code&gt;, and moved on with my life. &lt;/p&gt;

&lt;p&gt;Classic dev move: it works, ship it, never look inside.&lt;/p&gt;

&lt;p&gt;Then I found &lt;a href="https://github.com/mudler/edgevpn" rel="noopener noreferrer"&gt;EdgeVPN&lt;/a&gt;, a VPN written in Go that has &lt;strong&gt;no central server at all&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;You generate a token, paste it on two machines, and they find each other across the internet and start routing packets. &lt;/p&gt;

&lt;p&gt;No control plane, no &lt;code&gt;10.0.0.1&lt;/code&gt; server you have to keep alive, no cloud bill.&lt;/p&gt;

&lt;p&gt;That sounded suspiciously like magic, so I did the only reasonable thing and read the whole codebase. &lt;/p&gt;

&lt;p&gt;This post is me walking you through what I found, from "what even is a network interface" all the way down to how a single IP packet crosses the planet without anyone in charge.&lt;/p&gt;

&lt;p&gt;Grab a coffee. This one has depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, the uncomfortable truth about VPNs
&lt;/h2&gt;

&lt;p&gt;A VPN is not a tunnel. &lt;/p&gt;

&lt;p&gt;A VPN is a &lt;strong&gt;lie you tell your operating system&lt;/strong&gt;, and the OS is very willing to believe you.&lt;/p&gt;

&lt;p&gt;Here is the trick. &lt;/p&gt;

&lt;p&gt;Linux (and macOS, and Windows) lets you create a &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/TUN/TAP" rel="noopener noreferrer"&gt;TUN device&lt;/a&gt;&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;It looks like a normal network interface, it shows up in &lt;code&gt;ifconfig&lt;/code&gt;, it has an IP, the kernel happily routes traffic to it. &lt;/p&gt;

&lt;p&gt;But it is not connected to any hardware. &lt;/p&gt;

&lt;p&gt;It is connected to a file descriptor that your program holds.&lt;/p&gt;

&lt;p&gt;That is the whole thing. Really:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your program &lt;strong&gt;reads&lt;/strong&gt; from that file descriptor, and it gets IP packets the kernel wanted to send out.&lt;/li&gt;
&lt;li&gt;Your program &lt;strong&gt;writes&lt;/strong&gt; to that file descriptor, and the kernel believes those packets just arrived from the network.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else a VPN does (encryption, peer discovery, routing) is just deciding what happens in the gap between that read and that write. &lt;/p&gt;

&lt;p&gt;If you can move bytes from machine A's file descriptor to machine B's file descriptor, congratulations, you have written a VPN.&lt;/p&gt;

&lt;p&gt;Everything after this is engineering.&lt;/p&gt;

&lt;p&gt;Here is EdgeVPN's read loop, and it is honestly almost anticlimactic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ifce&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ethernet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="n"&gt;ethernet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Frame&lt;/span&gt;
    &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MTU&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ifce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"could not read from interface"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is it. That is the interface. &lt;code&gt;ifce&lt;/code&gt; is the TUN device, read gives you a raw packet, and now it is your problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi1d8j8owm7ws6z20uqf7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi1d8j8owm7ws6z20uqf7.png" alt=" " width="360" height="270"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The hard part is not tunneling, it is the phone book
&lt;/h2&gt;

&lt;p&gt;Now the real question. &lt;/p&gt;

&lt;p&gt;Machine A wants to send a packet to &lt;code&gt;10.1.0.13&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Machine A reads that packet off the TUN device. Cool. &lt;strong&gt;Where does it send it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;10.1.0.13&lt;/code&gt; is a made up address. &lt;/p&gt;

&lt;p&gt;It exists only inside your virtual network. &lt;/p&gt;

&lt;p&gt;Somewhere out there is a real machine, behind a real &lt;a href="https://en.wikipedia.org/wiki/Network_address_translation" rel="noopener noreferrer"&gt;NAT&lt;/a&gt;, with a real (and probably changing) public IP, that has claimed that made up address. &lt;/p&gt;

&lt;p&gt;Something has to map one to the other.&lt;/p&gt;

&lt;p&gt;WireGuard solves this by making you do it by hand. &lt;/p&gt;

&lt;p&gt;You write &lt;code&gt;AllowedIPs&lt;/code&gt; and an &lt;code&gt;Endpoint&lt;/code&gt; in a config file for every peer. &lt;/p&gt;

&lt;p&gt;It is simple, fast, and it does not scale to "I have twelve laptops behind twelve different routers and one of them is my friend's Raspberry Pi."&lt;/p&gt;

&lt;p&gt;Traditional VPNs solve it with a &lt;strong&gt;server&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The server knows everyone, everyone talks to the server, the server is the phone book. Also the server is the single point of failure, the single point of trust, and the single thing you have to pay for.&lt;/p&gt;

&lt;p&gt;EdgeVPN solves it with a &lt;strong&gt;gossiped ledger&lt;/strong&gt;, which is a fancy way of saying: every node keeps shouting what it knows, and everyone eventually agrees. Let me build up to it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The token is the entire network
&lt;/h2&gt;

&lt;p&gt;Everything starts with one command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;edgevpn &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; config.yaml
&lt;span class="c"&gt;# or, as a portable token&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;EDGEVPNTOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;edgevpn &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The token is just that YAML, base64'd. Nothing more. Decode one and you get roughly this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;otp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dht&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9000&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;7Kx...43-random-chars...&lt;/span&gt;
    &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;43&lt;/span&gt;
  &lt;span class="na"&gt;crypto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9000&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Qz9...43-random-chars...&lt;/span&gt;
    &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;43&lt;/span&gt;
&lt;span class="na"&gt;room&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bT2...43-random-chars...&lt;/span&gt;
&lt;span class="na"&gt;rendezvous&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mP4...43-random-chars...&lt;/span&gt;
&lt;span class="na"&gt;mdns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xL8...43-random-chars...&lt;/span&gt;
&lt;span class="na"&gt;max_message_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20971520&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Five random strings and two intervals. &lt;/p&gt;

&lt;p&gt;No IPs, no hostnames, no server address, no certificates. &lt;/p&gt;

&lt;p&gt;This file &lt;strong&gt;is&lt;/strong&gt; your network. &lt;/p&gt;

&lt;p&gt;Which brings us to the most important sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt; Exposing this file or passing-it by is equivalent to give full control to the network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hold that thought, we are coming back to it with receipts.&lt;/p&gt;

&lt;p&gt;The clever bit is those &lt;code&gt;otp&lt;/code&gt; blocks. &lt;/p&gt;

&lt;p&gt;Those keys are not used directly. &lt;/p&gt;

&lt;p&gt;They are fed into &lt;strong&gt;TOTP&lt;/strong&gt;, the same algorithm behind the 6 digit codes in your authenticator app, except tuned to spit out a long base64 string instead:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TOTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;otp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Hash&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c"&gt;// sha256 here&lt;/span&gt;
        &lt;span class="n"&gt;Digits&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// 43&lt;/span&gt;
        &lt;span class="n"&gt;TimeStep&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;otp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c"&gt;// 9000 seconds&lt;/span&gt;
        &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nb&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdEncoding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EncodeToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TOTP&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So every 9000 seconds (2.5 hours), every node in the network independently derives &lt;strong&gt;the same new secret&lt;/strong&gt;, without talking to each other. &lt;/p&gt;

&lt;p&gt;Time is the only coordination they need. This gets used for two different things, and both are neat.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: finding each other at a rotating rendezvous
&lt;/h2&gt;

&lt;p&gt;To meet a stranger on the internet you need a place to meet. &lt;/p&gt;

&lt;p&gt;EdgeVPN uses the &lt;strong&gt;&lt;a href="https://github.com/libp2p/go-libp2p-kad-dht" rel="noopener noreferrer"&gt;IPFS Kademlia DHT&lt;/a&gt;&lt;/strong&gt; as that meeting point, announcing itself under a key that is just &lt;code&gt;TOTP(dht.key)&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;So your rendezvous on a &lt;strong&gt;public&lt;/strong&gt; hash table moves every couple of hours. &lt;/p&gt;

&lt;p&gt;A watcher sees peers cluster around a random looking key, then that key goes cold forever.&lt;/p&gt;

&lt;p&gt;One subtle trap, nicely handled: if everyone rotates at exactly time T, a node with a skewed clock rotates early and for a moment nobody finds anybody. &lt;/p&gt;

&lt;p&gt;So the DHT announces on the last two rendezvous points at once:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;rendezvousHistory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Ring&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Overlapping windows. Unglamorous, and exactly the difference between "works in the demo" and "works at 3am". &lt;/p&gt;

&lt;p&gt;On a LAN, mDNS does the same job with the &lt;code&gt;mdns&lt;/code&gt; string as the service tag.&lt;/p&gt;

&lt;p&gt;Here is the full bootstrap:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbmturqdr7c5q4212x7jd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbmturqdr7c5q4212x7jd.png" alt=" " width="800" height="895"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the ordering. Peers connect first, and &lt;strong&gt;then&lt;/strong&gt; they exchange the routing table. &lt;/p&gt;

&lt;p&gt;The routing table travels over the p2p network it describes. Bootstrapping is fun.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: the gossip room, sealed twice
&lt;/h2&gt;

&lt;p&gt;Connected peers join a &lt;a href="https://docs.libp2p.io/concepts/pubsub/overview/" rel="noopener noreferrer"&gt;GossipSub&lt;/a&gt; topic named from the other OTP. &lt;/p&gt;

&lt;p&gt;libp2p already encrypts every connection, and EdgeVPN still wraps &lt;strong&gt;another&lt;/strong&gt; AES layer around each message, keyed the same rotating way:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;sealkey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;internalCrypto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MD5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internalCrypto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TOTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SealKeyLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SealKeyInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExchangeKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Why double up? The rendezvous is public, so anyone can find the topic and subscribe. &lt;/p&gt;

&lt;p&gt;Transport encryption protects the hop, not the payload from someone who legitimately joined the room. &lt;/p&gt;

&lt;p&gt;The seal means a lurker hears noise, and since the key retires every 2.5 hours, brute forcing captured traffic chases a key nobody uses anymore.&lt;/p&gt;

&lt;p&gt;Layers of secrets, rotating on a timer, all from one shared string. Most elegant idea in the codebase.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: the ledger, or "eventual consistency by shouting"
&lt;/h2&gt;

&lt;p&gt;Now the phone book. EdgeVPN keeps a tiny blockchain. Before you close the tab: no proof of work, no mining, no coin. &lt;/p&gt;

&lt;p&gt;It is a chain of blocks purely for cheap ordering, and each block carries a &lt;code&gt;map[bucket]map[key]value&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The buckets are hardcoded constants that read like a table of contents for the whole product:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;FilesLedgerKey&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"files"&lt;/span&gt;
    &lt;span class="n"&gt;MachinesLedgerKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"machines"&lt;/span&gt;
    &lt;span class="n"&gt;ServicesLedgerKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"services"&lt;/span&gt;
    &lt;span class="n"&gt;UsersLedgerKey&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"users"&lt;/span&gt;
    &lt;span class="n"&gt;HealthCheckKey&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"healthcheck"&lt;/span&gt;
    &lt;span class="n"&gt;DNSKey&lt;/span&gt;            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"dns"&lt;/span&gt;
    &lt;span class="n"&gt;EgressService&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"egress"&lt;/span&gt;
    &lt;span class="n"&gt;TrustZoneKey&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"trustzone"&lt;/span&gt;
    &lt;span class="n"&gt;TrustZoneAuthKey&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"trustzoneAuth"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The routing table is just the &lt;code&gt;machines&lt;/code&gt; bucket keyed by virtual IP. &lt;/p&gt;

&lt;p&gt;Every feature here is "write to a bucket, read other people's writes". &lt;/p&gt;

&lt;p&gt;Once that clicked the codebase went from clever to obvious, which is the best thing you can say about an architecture.&lt;/p&gt;

&lt;p&gt;Conflict resolution is beautifully blunt:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blockchain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Last&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hash&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blockchain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Highest block wins, ties broken by higher hash so every node breaks them identically. Whole block replace, no vector clocks, no Raft.  &lt;/p&gt;

&lt;p&gt;"But that loses writes," you say. Yes! And it does not matter, because every node reannounces its own state on a ticker until it sees itself reflected in the chain. &lt;/p&gt;

&lt;p&gt;Read it, check if the world agrees with you, and if not, say it again. Louder. Forever. &lt;/p&gt;

&lt;p&gt;The system converges not because the merge is smart but because everyone is relentlessly repetitive. &lt;/p&gt;

&lt;p&gt;Wipe the entire chain and the nodes refill it in seconds.&lt;/p&gt;

&lt;p&gt;The tradeoff is real and the docs own it: every block goes to every peer, so this is chatty by design. &lt;/p&gt;

&lt;p&gt;Fine for a routing table, which is why the README warns you off latency sensitive workloads.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4: an actual packet, end to end
&lt;/h2&gt;

&lt;p&gt;Here is the life of one ping, with the real function names.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2jg3t8p6w6ue1dztc1an.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2jg3t8p6w6ue1dztc1an.png" alt=" " width="744" height="1986"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The lookup is where every piece meets:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MachinesLedgerKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;notFoundErr&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;machine&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Decode the Peer&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PeerID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Virtual IP goes in, libp2p peer ID comes out. &lt;/p&gt;

&lt;p&gt;That one lookup replaces the entire VPN server, and libp2p handles the rest: NAT traversal, hole punching, relays, encryption, multiplexing.&lt;/p&gt;

&lt;p&gt;The receiving end checks the sender against that same ledger before copying a byte, and resets the stream otherwise. &lt;/p&gt;

&lt;p&gt;If you are not in my routing table, I will not read your packets. &lt;/p&gt;

&lt;p&gt;Two jobs, one map.&lt;/p&gt;
&lt;h2&gt;
  
  
  The bit I did not expect to enjoy: leaderless leader election
&lt;/h2&gt;

&lt;p&gt;Every node drops a timestamp into a &lt;code&gt;healthcheck&lt;/code&gt; bucket, and anyone older than &lt;code&gt;maxtime&lt;/code&gt; counts as gone.&lt;/p&gt;

&lt;p&gt;But some jobs need exactly one node to run them (scrubbing stale entries, handing out IPs). &lt;/p&gt;

&lt;p&gt;With no server, who decides? This:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Leader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actives&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;leaderboard&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;leader&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;actives&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;actives&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;leaderboard&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// fnv32a&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;leader&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;leader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;leader&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Hash every live peer ID, highest wins. No votes, no terms, no Raft. &lt;/p&gt;

&lt;p&gt;Every node computes it locally and agrees, because the ledger already synced the input. &lt;/p&gt;

&lt;p&gt;When the leader dies it falls out of the healthcheck bucket, everyone recomputes, and a new leader appears with zero messages exchanged.&lt;/p&gt;

&lt;p&gt;Is it Paxos? No. Does deciding who deletes stale map entries need Paxos? Also no. Right sized engineering is underrated.&lt;/p&gt;

&lt;p&gt;Same trick powers the built in DHCP: nodes without an IP wait, the leader among them takes the lowest free one, next node repeats.&lt;/p&gt;

&lt;p&gt;Distributed DHCP with no DHCP server, built from a hash function and patience.&lt;/p&gt;
&lt;h2&gt;
  
  
  Now, about that token
&lt;/h2&gt;

&lt;p&gt;Remember that warning. In the default config, holding the token lets you write anything into any bucket. Including this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;machines/&lt;/span&gt;&lt;span class="mf"&gt;10.1&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;PeerID:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-peer-id-actually"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Congratulations, you just hijacked someone's virtual IP and every node will happily route their traffic to you. &lt;/p&gt;

&lt;p&gt;The routing table has no concept of who owns which key.&lt;/p&gt;

&lt;p&gt;That is the sort of thing you only catch by reading source, and to their credit the project keeps layering in defenses. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PeerGuardian&lt;/strong&gt; (&lt;code&gt;pkg/trustzone&lt;/code&gt;) adds ECDSA challenge and response, so the token alone does not get you in. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relay ACL&lt;/strong&gt; (&lt;code&gt;pkg/config/relay_acl.go&lt;/code&gt;) gates who can use you as a circuit relay, with an open bootstrap window because a NAT'd peer often needs a relay &lt;em&gt;before&lt;/em&gt; it can prove membership. Chicken and egg, at the network layer.&lt;/p&gt;

&lt;p&gt;The good one is &lt;strong&gt;ledger ownership&lt;/strong&gt; (&lt;code&gt;pkg/blockchain/sign.go&lt;/code&gt;): sign every entry with the node's libp2p key and enforce per key ownership on merge, in three modes, &lt;code&gt;off&lt;/code&gt;, &lt;code&gt;observe&lt;/code&gt;, &lt;code&gt;enforce&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;That middle mode is the detail I liked most. &lt;/p&gt;

&lt;p&gt;Turn it on in a live network, watch the violation logs for a week, then flip to enforce. &lt;/p&gt;

&lt;p&gt;That is how you ship a breaking security change to something you cannot restart all at once.&lt;/p&gt;

&lt;p&gt;The README also says it plainly: &lt;em&gt;"I'm not a security expert, and this software didn't went through a full security audit"&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Respect for that instead of stamping "military grade encryption" on a landing page.&lt;/p&gt;
&lt;h2&gt;
  
  
  The layer cake
&lt;/h2&gt;

&lt;p&gt;Zooming out, here is how the whole thing stacks. &lt;/p&gt;

&lt;p&gt;Everything above the node is optional and plugs into the same two extension points:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2rp6mmvvx9v0ra6nqxhz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2rp6mmvvx9v0ra6nqxhz.png" alt=" " width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dotted lines matter. Control traffic (who owns what) goes through the gossip ledger, slowly and to everyone. &lt;/p&gt;

&lt;p&gt;Data traffic (your actual packets) goes point to point over a dedicated libp2p stream protocol, directly to the one peer that needs it. &lt;/p&gt;

&lt;p&gt;Mixing those up is how you build something that falls over at four nodes.&lt;/p&gt;

&lt;p&gt;The extension seam is two types, and once you see them you can read any feature in the repo:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// something long running that gets the node and the ledger&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;NetworkService&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;blockchain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ledger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;

&lt;span class="c"&gt;// something that handles a raw stream for a given protocol ID&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;StreamHandler&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;blockchain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ledger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The VPN is a &lt;code&gt;NetworkService&lt;/code&gt;. So is DNS. So is the file transfer. So is the healthcheck. &lt;/p&gt;

&lt;p&gt;Adding a feature means picking a bucket name, announcing into it, and optionally claiming a protocol ID for the data path. &lt;/p&gt;

&lt;p&gt;That is the whole framework.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89n1otobo4j1azhqsfcv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89n1otobo4j1azhqsfcv.png" alt=" " width="360" height="312"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What I actually took away
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Coordination is the actual product.&lt;/strong&gt; WireGuard says "you coordinate, I will be fast". Commercial VPNs say "a server coordinates, pay us". EdgeVPN says "we gossip until we agree". The question was never encryption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeating yourself beats being clever.&lt;/strong&gt; Every node reannounces its own truth forever, so the merge logic gets to be ten dumb lines and still heals from total data loss. Idempotent shouting as a consistency model. I am stealing this.&lt;/p&gt;

&lt;p&gt;Go read some source code. The scariest part of most systems is the part you have not looked at yet, and it is usually about 300 lines.&lt;/p&gt;

&lt;p&gt;Repo is &lt;a href="https://github.com/mudler/edgevpn" rel="noopener noreferrer"&gt;github.com/mudler/edgevpn&lt;/a&gt;, docs are &lt;a href="https://mudler.github.io/edgevpn" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and &lt;a href="https://docs.libp2p.io/" rel="noopener noreferrer"&gt;libp2p's docs&lt;/a&gt; are where the real dark magic lives.&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>programming</category>
      <category>linux</category>
      <category>networking</category>
    </item>
    <item>
      <title>How terminal-sharing tools put your shell in a browser</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Sun, 26 Jul 2026 16:43:17 +0000</pubDate>
      <link>https://dev.to/lovestaco/how-terminal-sharing-tools-put-your-shell-in-a-browser-328</link>
      <guid>https://dev.to/lovestaco/how-terminal-sharing-tools-put-your-shell-in-a-browser-328</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You have probably done this dance. &lt;/p&gt;

&lt;p&gt;You are debugging something on a box, your teammate says "can you just show me", and you end up screen sharing a 4K monitor over a video call so they can squint at 11px monospace text that has been compressed into oatmeal.&lt;/p&gt;

&lt;p&gt;There is a better way, and it has existed for years. &lt;/p&gt;

&lt;p&gt;Tools like &lt;a href="https://github.com/tsl0922/ttyd" rel="noopener noreferrer"&gt;ttyd&lt;/a&gt;, &lt;a href="https://github.com/cs01/termpair" rel="noopener noreferrer"&gt;TermPair&lt;/a&gt;, and &lt;a href="https://github.com/ekzhang/sshx" rel="noopener noreferrer"&gt;sshx&lt;/a&gt; put your actual terminal in someone else's browser. Real text. Real selection. &lt;/p&gt;

&lt;p&gt;Real copy paste.&lt;/p&gt;

&lt;p&gt;I got curious about how they pull this off, so I cloned all three and read them. &lt;/p&gt;

&lt;p&gt;Turns out they are solving the same problem in three very different ways, and the differences are genuinely interesting. &lt;/p&gt;

&lt;p&gt;Let's get shell shocked together.&lt;/p&gt;

&lt;h2&gt;
  
  
  what even is a terminal
&lt;/h2&gt;

&lt;p&gt;Before any of the web stuff makes sense, you need one concept: the &lt;strong&gt;pseudoterminal&lt;/strong&gt;, or PTY.&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;bash&lt;/code&gt; in your terminal app, bash is not talking to your keyboard. &lt;/p&gt;

&lt;p&gt;It is talking to a file. &lt;/p&gt;

&lt;p&gt;Specifically, a pair of linked file descriptors that the kernel sets up to impersonate an actual physical teletype from 1965.&lt;/p&gt;

&lt;p&gt;One end is the &lt;strong&gt;master&lt;/strong&gt;, one end is the &lt;strong&gt;slave&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Your terminal emulator holds the master. Bash holds the slave, and bash has no idea it is being catfished.&lt;/p&gt;

&lt;p&gt;Anything you write to the master shows up as keyboard input to bash. Anything bash prints comes back out of the master. &lt;/p&gt;

&lt;p&gt;That is the entire trick. &lt;/p&gt;

&lt;p&gt;Terminal sharing tools are just programs that hold the master end and forward those bytes somewhere more interesting than a window on your laptop.&lt;/p&gt;

&lt;p&gt;Here is sshx doing exactly that, in &lt;code&gt;crates/sshx/src/terminal/unix.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;nix&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;pty&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Winsize&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;nix&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;unistd&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;execvp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ForkResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pid&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;nix&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;login_tty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TIOCGWINSZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TIOCSWINSZ&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Terminal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;master_read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;master_write&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Fork, call &lt;code&gt;login_tty&lt;/code&gt; in the child so the slave fd becomes its controlling terminal, &lt;code&gt;execvp&lt;/code&gt; the shell, and keep the master in the parent. &lt;/p&gt;

&lt;p&gt;ttyd does the same thing in C. &lt;/p&gt;

&lt;p&gt;TermPair does it in Rust. &lt;/p&gt;

&lt;p&gt;Three codebases, one ancient POSIX handshake. &lt;/p&gt;

&lt;p&gt;Everyone takes the same fork in the road.&lt;/p&gt;

&lt;p&gt;Oh, and &lt;code&gt;TIOCSWINSZ&lt;/code&gt; is how the terminal size gets set. &lt;/p&gt;

&lt;p&gt;When you resize your browser window, that ioctl is what eventually fires so that &lt;code&gt;vim&lt;/code&gt; redraws correctly instead of smearing itself across the screen. &lt;/p&gt;

&lt;p&gt;Resize handling is not a nice-to-have here, it is load bearing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdi8etifapyhedgmi8pzf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdi8etifapyhedgmi8pzf.png" alt=" " width="360" height="258"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The simplest possible version: ttyd
&lt;/h2&gt;

&lt;p&gt;ttyd is the "no, seriously, that's it" implementation. &lt;/p&gt;

&lt;p&gt;One C process, built on libwebsockets and libuv. &lt;/p&gt;

&lt;p&gt;It &lt;strong&gt;is&lt;/strong&gt; the web server and it &lt;strong&gt;is&lt;/strong&gt; the terminal host. &lt;/p&gt;

&lt;p&gt;There is no relay, no separate client, no key exchange.&lt;/p&gt;

&lt;p&gt;The protocol is beautifully dumb. From &lt;code&gt;src/server.h&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// client -&amp;gt; server&lt;/span&gt;
&lt;span class="cp"&gt;#define INPUT           '0'
#define RESIZE_TERMINAL '1'
#define PAUSE           '2'
#define RESUME          '3'
#define JSON_DATA       '{'
&lt;/span&gt;
&lt;span class="c1"&gt;// server -&amp;gt; client&lt;/span&gt;
&lt;span class="cp"&gt;#define OUTPUT            '0'
#define SET_WINDOW_TITLE  '1'
#define SET_PREFERENCES   '2'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Single byte opcode, then payload. That is the whole wire format. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;'0'&lt;/code&gt; plus your keystrokes goes up, &lt;code&gt;'0'&lt;/code&gt; plus terminal output comes down. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;PAUSE&lt;/code&gt; and &lt;code&gt;RESUME&lt;/code&gt; exist because a fast-scrolling &lt;code&gt;cat&lt;/code&gt; of a huge file can outrun the browser, so the client can apply backpressure instead of dying.&lt;/p&gt;

&lt;p&gt;Here is the full round trip:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzl5b3y57xplscd3yl8vz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzl5b3y57xplscd3yl8vz.png" alt=" " width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two details worth stealing from ttyd:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The frontend ships inside the binary.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The Preact and xterm.js app in &lt;code&gt;html/&lt;/code&gt; gets bundled, gzipped, and inlined into &lt;code&gt;src/html.h&lt;/code&gt; as a C byte array. &lt;/p&gt;

&lt;p&gt;You get a single executable with no static file directory to misplace. &lt;/p&gt;

&lt;p&gt;The tradeoff is that touching a &lt;code&gt;.tsx&lt;/code&gt; file means running &lt;code&gt;yarn run inline&lt;/code&gt; and then rebuilding the C binary, which surprises people exactly once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is read only by default.&lt;/strong&gt; You have to pass &lt;code&gt;-W&lt;/code&gt; to let viewers actually type.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ttyd &lt;span class="nt"&gt;-p&lt;/span&gt; 7681 &lt;span class="nt"&gt;-c&lt;/span&gt; user:pass &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="nt"&gt;-W&lt;/span&gt; bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That said, ttyd's threat model is "you trust the server, because the server is your machine." &lt;/p&gt;

&lt;p&gt;The bytes are plaintext over the wire unless you turn on TLS. &lt;/p&gt;

&lt;p&gt;Which brings us to the interesting question.&lt;/p&gt;
&lt;h2&gt;
  
  
  What if you don't trust the server?
&lt;/h2&gt;

&lt;p&gt;The moment you want to share a terminal with someone across the internet, you need a middleman with a public IP. And now that middleman can read everything you type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcmdgep1xg6kys57dq3g2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcmdgep1xg6kys57dq3g2.png" alt=" " width="360" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TermPair and sshx both took that guy's advice. &lt;/p&gt;

&lt;p&gt;They make the server a &lt;strong&gt;blind relay&lt;/strong&gt;: it routes ciphertext between the terminal host and the browsers, and it structurally cannot decrypt any of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fldkushytdmid38vhrz2n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fldkushytdmid38vhrz2n.png" alt=" " width="800" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Green means "can read your terminal."&lt;/p&gt;

&lt;p&gt;The whole game is shrinking the green.&lt;/p&gt;
&lt;h2&gt;
  
  
  The key delivery trick
&lt;/h2&gt;

&lt;p&gt;So the client encrypts and the browser decrypts. Both need the key. &lt;/p&gt;

&lt;p&gt;The server must not have it. But the browser gets its entire existence from the server. &lt;/p&gt;

&lt;p&gt;How do you hand a secret to a page that the server itself sent you?&lt;/p&gt;

&lt;p&gt;The answer is a URL fragment.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://sharemyclau.de/s/abc123#key-goes-right-here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Everything after &lt;code&gt;#&lt;/code&gt; is &lt;strong&gt;never sent in the HTTP request&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;It is a client side construct. &lt;/p&gt;

&lt;p&gt;Browsers use it for anchor scrolling and hash routing, and it stays in the address bar and in JavaScript's &lt;code&gt;location.hash&lt;/code&gt; while the server sees only &lt;code&gt;/s/abc123&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;So you share one link, the recipient's browser reads the key out of its own address bar, and the relay in the middle sees an opaque session ID and a stream of noise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4c4spju4tjytrb8dz4hd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4c4spju4tjytrb8dz4hd.png" alt=" " width="360" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the flow end to end:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuxsn896cax51ugjrly2r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuxsn896cax51ugjrly2r.png" alt=" " width="800" height="821"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The security of this rests entirely on the link. Anyone who gets the full URL gets the session. &lt;/p&gt;

&lt;p&gt;Don't paste it in a public Slack channel and then act surprised.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two flavors of crypto
&lt;/h2&gt;

&lt;p&gt;TermPair and sshx both do AES, but they made different calls, and the differences are instructive.&lt;/p&gt;
&lt;h3&gt;
  
  
  TermPair: AES-128-GCM with counter IVs
&lt;/h3&gt;

&lt;p&gt;From &lt;code&gt;crates/termpair-common/src/encryption.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;iv_from_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;IV_LENGTH&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;IV_LENGTH&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="nf"&gt;.to_le_bytes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="nf"&gt;.copy_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;iv&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;GCM gives you authentication for free, so a tampered message fails to decrypt rather than quietly turning into garbage that your terminal then interprets as escape sequences. &lt;/p&gt;

&lt;p&gt;The IV is a plain message counter, which is fine and correct &lt;strong&gt;as long as you never reuse one with the same key&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Nonce reuse in GCM is not a "slightly weaker" situation, it is a "here is your key, thanks for playing" situation.&lt;/p&gt;

&lt;p&gt;Which is why &lt;code&gt;constants.rs&lt;/code&gt; has this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ROTATION_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MAX_MESSAGES_PER_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ROTATION_THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After about a million messages, the key rotates. &lt;/p&gt;

&lt;p&gt;There is a whole &lt;code&gt;aes_key_rotation&lt;/code&gt; event in the protocol for it. &lt;/p&gt;

&lt;p&gt;Separate keys for terminal output and browser input, plus a bootstrap key for the handshake, so the counter spaces cannot collide. &lt;/p&gt;

&lt;p&gt;It is careful work, and the kind of thing that is invisible when it is done right.&lt;/p&gt;
&lt;h3&gt;
  
  
  sshx: Argon2 stretched key with AES-CTR
&lt;/h3&gt;

&lt;p&gt;sshx made a different bet. From &lt;code&gt;crates/sshx/src/encrypt.rs&lt;/code&gt;, with a comment I really enjoyed:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SALT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="s"&gt;"This is a non-random salt for sshx.io, since we want to stretch the security of 83-bit keys!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;hasher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Argon2&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nn"&gt;Algorithm&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Argon2id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;V0x13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The key in your URL is only 83 bits of entropy, because sshx wants the shareable link to stay short enough to actually paste in chat. &lt;/p&gt;

&lt;p&gt;83 bits is not a lot by modern standards, so it runs Argon2id over it with real memory cost parameters. Brute forcing now costs 19MB and real CPU time per guess instead of a nanosecond. &lt;/p&gt;

&lt;p&gt;It is a deliberate trade of raw entropy for ergonomics, paid back with a KDF.&lt;/p&gt;

&lt;p&gt;Then it uses CTR mode with a clever addressing scheme:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream_num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;assert_ne!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"stream number must be nonzero"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// security check&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.copy_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;stream_num&lt;/span&gt;&lt;span class="nf"&gt;.to_be_bytes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Aes128Ctr64BE&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.aes_key&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="nf"&gt;.seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="nf"&gt;.apply_keystream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Because CTR mode is seekable, sshx can encrypt "the bytes at offset N of stream M" without touching anything before them. &lt;/p&gt;

&lt;p&gt;That is not a crypto flex, it is an architecture decision: it means a browser that reconnects can say "I have everything up to byte 4096, send me the rest" and the server can serve that from its buffer. &lt;/p&gt;

&lt;p&gt;Encryption and resumability designed together.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;assert_ne!(stream_num, 0)&lt;/code&gt; guard is there because stream 0 is used for the encrypted zeros block that proves you have the right key. &lt;/p&gt;

&lt;p&gt;Reusing it would mean reusing a keystream. &lt;/p&gt;

&lt;p&gt;Nice to see the security check written down instead of assumed.&lt;/p&gt;
&lt;h2&gt;
  
  
  The part that makes sshx feel fast
&lt;/h2&gt;

&lt;p&gt;Here is my favorite detail in any of these codebases.&lt;/p&gt;

&lt;p&gt;There is a fundamental problem with remote terminals: your keystroke has to go to the server, into the PTY, back out, and back to you before you see the character. &lt;/p&gt;

&lt;p&gt;On a 200ms link, typing feels like moving through syrup.&lt;/p&gt;

&lt;p&gt;sshx solves this the way &lt;a href="https://mosh.org/" rel="noopener noreferrer"&gt;Mosh&lt;/a&gt; does, with &lt;strong&gt;predictive echo&lt;/strong&gt;. From &lt;code&gt;src/lib/typeahead.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A terminal "local echo" or typeahead addon for xterm.js.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// This is forked from VSCode's typeahead implementation at&lt;/span&gt;
&lt;span class="c1"&gt;// https://github.com/microsoft/vscode/blob/1.80.1/...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The client guesses. You press &lt;code&gt;k&lt;/code&gt;, it draws a &lt;code&gt;k&lt;/code&gt; immediately (slightly dimmed) while the real round trip happens in the background. &lt;/p&gt;

&lt;p&gt;When the server's actual output arrives, it reconciles. &lt;/p&gt;

&lt;p&gt;If the guess was right, nothing visibly changes. &lt;/p&gt;

&lt;p&gt;If it was wrong, because you were in &lt;code&gt;vim&lt;/code&gt; or hitting a password prompt, it rolls back.&lt;/p&gt;

&lt;p&gt;The tricky part is knowing when &lt;strong&gt;not&lt;/strong&gt; to predict. &lt;/p&gt;

&lt;p&gt;Predicting into a password prompt would echo your password onto the screen, which is a memorable way to lose a friend. &lt;/p&gt;

&lt;p&gt;So the addon tracks terminal modes and bails out when it is not confident.&lt;/p&gt;

&lt;p&gt;Getting a-head of the output, but politely.&lt;/p&gt;
&lt;h2&gt;
  
  
  Sessions, state, and the boring stuff that matters
&lt;/h2&gt;

&lt;p&gt;The last layer is lifecycle. Sessions are long lived, connections are not. Wifi drops. Laptops sleep.&lt;/p&gt;

&lt;p&gt;sshx handles this with sequence numbers in the protocol (&lt;code&gt;crates/sshx-core/proto/sshx.proto&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;TerminalData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;uint32&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;bytes&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uint64&lt;/span&gt; &lt;span class="na"&gt;seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Sequence number of the first byte&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;SequenceNumbers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="na"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Active shells and their sequence numbers&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every shell is a stream with a byte offset. On reconnect, the client sends where it left off and the server replays the difference. &lt;/p&gt;

&lt;p&gt;Combined with seekable CTR encryption, resume is nearly free.&lt;/p&gt;

&lt;p&gt;It also has to decide when to give up on a session:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cd"&gt;/// Timeout for a disconnected session to be evicted and closed.&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;DISCONNECTED_SESSION_EXPIRY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Five minutes without a backend client and the session is closed and dropped from the &lt;code&gt;DashMap&lt;/code&gt;. Otherwise every crashed laptop leaks memory forever.&lt;/p&gt;

&lt;p&gt;And because sshx runs a globally distributed mesh, session state also goes into Redis via &lt;code&gt;crates/sshx-server/src/state/mesh.rs&lt;/code&gt;, so you can connect to the nearest server rather than always crossing an ocean. &lt;/p&gt;

&lt;p&gt;TermPair, meanwhile, caps things with hard limits (&lt;code&gt;MAX_TERMINALS: 200&lt;/code&gt;, &lt;code&gt;MAX_BROWSERS_PER_TERMINAL: 50&lt;/code&gt;, &lt;code&gt;MAX_WS_MSGS_PER_SEC: 500&lt;/code&gt;) which is the sensible answer when you are running one modest box instead of a mesh.&lt;/p&gt;
&lt;h2&gt;
  
  
  So which one should you use
&lt;/h2&gt;

&lt;p&gt;Genuinely depends on what you are doing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ttyd&lt;/strong&gt; if the network is already trusted. Homelab, LAN, behind a VPN, embedded devices, an OpenWrt router. It is one small C binary with no runtime dependencies and it will outlive us all. &lt;code&gt;ttyd -W bash&lt;/code&gt; and you are done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TermPair&lt;/strong&gt; if you want end to end encryption with a simple mental model and the option to self host the relay. The three-key design with rotation is easy to reason about and the code is small enough to read in an afternoon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sshx&lt;/strong&gt; if you want the polished product experience. Multiple terminals on an infinite canvas, other people's cursors moving in real time, predictive echo, global mesh, automatic reconnect. Worth noting the README says self hosting is explicitly not supported, so this is "use sshx.io" rather than "run your own."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw0qrnllfu3xtqgzn815h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw0qrnllfu3xtqgzn815h.png" alt=" " width="360" height="312"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Every one of these tools is, at its core, &lt;code&gt;fork()&lt;/code&gt;, a PTY master fd, and a loop that shovels bytes into a WebSocket. &lt;/p&gt;

&lt;p&gt;That part is 1970s technology wearing a 2020s hoodie.&lt;/p&gt;

&lt;p&gt;Everything else, the encryption, the sequence numbers, the key rotation, the predictive echo, the session eviction, exists to answer one question: &lt;strong&gt;how much do you trust the machine in the middle?&lt;/strong&gt; ttyd says "it's mine, so completely."&lt;/p&gt;

&lt;p&gt;TermPair and sshx say "not at all, and here is the math."&lt;/p&gt;

&lt;p&gt;Reading three implementations of the same idea side by side is the fastest architecture lesson I have had in a while.&lt;/p&gt;

&lt;p&gt;If a concept keeps showing up in every version, it is essential. &lt;/p&gt;

&lt;p&gt;If it only shows up in one, it is a product decision. &lt;/p&gt;

&lt;p&gt;That distinction is hard to see from a single codebase and obvious from three.&lt;/p&gt;

&lt;p&gt;Go clone them. &lt;/p&gt;

&lt;p&gt;It is more fun than another framework tutorial, and you will never look at &lt;code&gt;location.hash&lt;/code&gt; the same way again.&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
      <category>cli</category>
    </item>
  </channel>
</rss>
